# Source code for tecplot.data.facemap

    import itertools as it
    import logging

    from collections import deque, namedtuple
    from contextlib import contextmanager
    from ctypes import c_int32, c_int64, c_void_p, POINTER, addressof, cast
    from textwrap import dedent

    from ..tecutil import _tecutil
    from ..constant import *
    from ..exception import *
    from .. import version
    from ..tecutil import array_to_str, lock, lock_attributes

    log = logging.getLogger(__name__)

    @lock_attributes
    class Elementmap(c_void_p):
        """Facemap reverse look-up."""
        def __init__(self, zone):
            self.zone = zone
            super().__init__(self._native_reference())

        @lock()
        def _native_reference(self):
            with self.zone.dataset.frame.activated():
                return _tecutil.DataElemGetReadableRef(self.zone.index + 1)

        def num_faces(self, element):
            return _tecutil.DataElemGetNumFaces(self, element + 1)

        def face(self, element, offset):
            return _tecutil.DataElemGetFace(self, element + 1, offset + 1) - 1

        def faces(self, element):
            return [self.face(element, f) for f in range(self.num_faces(element))]

    @lock_attributes
    class Facemap(c_void_p):
        r"""Connectivity list definition and control.

        A facemap holds the connectivity for a polytopal finite-element zone. This
        includes node-to-element and element-to-element connections. The following
        script creates a quad of two triangles from scratch using the PyTecplot
        low-level data creation interface. The data created looks like this:

        .. code-block:: none

            Node positions (x,y,z):

                           (1,1,1)
                          3
                         / \
                        /   \
             (0,1,.5)  2-----1  (1,0,.5)
                        \   /
                         \ /
                          0
                           (0,0,0)

        Element indices are used when identifying the left and right of each face,
        where :math:`-1` is used to indicate no element:

        .. code-block:: none

                          *
                      -1 / \ -1
                        / 1 \
                       *-----*
                        \ 0 /
                      -1 \ / -1
                          *

        .. code-block:: python
            :emphasize-lines: 28

            import tecplot as tp
            from tecplot.constant import *

            nodes = ((0, 0, 0  ),
                     (1, 0, 0.5),
                     (0, 1, 0.5),
                     (1, 1, 1  ))
            faces = ((0, 1),
                     (1, 2),
                     (2, 0),
                     (1, 3),
                     (3, 2))
            elements = (( 0, 0,  0,  1,  1),  # elements to the left of each face
                        (-1, 1, -1, -1, -1))  # elements to the right of each face
            num_elements = 2
            scalar_data = (0, 1, 2, 3)

            ds = tp.active_frame().create_dataset('Data', ['x','y','z','s'])
            z = ds.add_poly_zone(ZoneType.FEPolygon,
                                 name='FE Polygon Float (4,2,5) Nodal',
                                 num_points=len(nodes),
                                 num_elements=num_elements,
                                 num_faces=len(faces))

            z.values('x')[:] = [n[0] for n in nodes]
            z.values('y')[:] = [n[1] for n in nodes]
            z.values('z')[:] = [n[2] for n in nodes]
            z.facemap.set_mapping(faces, elements)
            z.values('s')[:] = scalar_data

            ### setup a view of the data
            plot = tp.active_frame().plot(PlotType.Cartesian3D)
            plot.activate()

            cont = plot.contour(0)
            cont.colormap_name = 'Sequential - Yellow/Green/Blue'
            cont.colormap_filter.distribution = ColorMapDistribution.Continuous

            for ax in plot.axes:
                ax.show = True

            plot.show_mesh = False
            plot.show_contour = True
            plot.show_edge = True
            plot.use_translucency = True

            fmap = plot.fieldmap(z)
            fmap.surfaces.surfaces_to_plot = SurfacesToPlot.All
            fmap.effects.surface_translucency = 40

            # View parameters obtained interactively from Tecplot 360
            plot.view.distance = 10
            plot.view.width = 2
            plot.view.psi = 80
            plot.view.theta = 30
            plot.view.alpha = 0
            plot.view.position = (-4.2, -8.0, 2.3)

            # Turning on mesh, we can see all the individual triangles
            plot.show_mesh = True
            plot.fieldmap(z).mesh.line_pattern = LinePattern.Dashed

            cont.levels.reset_to_nice()
            tp.export.save_png('polygons1.png', 600, supersample=3)

        ..  figure:: /_static/images/polygons1.png
            :width: 300px
            :figwidth: 300px

            Two triangle polygons showing edge and mesh lines.
        """
        def __init__(self, zone):
            if __debug__:
                sdk_required = (2017, 2, 0)
                if (
                    version.sdk_version_info < sdk_required and
                    zone.num_faces == 0
                ):
                    msg = 'Deferred allocation of facemap not supported.'
                    raise TecplotOutOfDateEngineError(sdk_required, msg)
            self.zone = zone
            if self.zone.num_faces > 0:
                try:
                    ref = self._native_reference()
                except:
                    ref = None
            else:
                ref = None
            super().__init__(ref)

        @property
        def _has_data_backing(self):
            return bool(self)

        @lock()
        def _native_reference(self, writable=False):
            try:
                _dispatch = {
                    True: _tecutil.DataFaceMapGetWritableRef,
                    False: _tecutil.DataFaceMapGetReadableRef}
                with self.zone.dataset.frame.activated():
                    return _dispatch[writable](self.zone.index + 1)
            except TecplotLogicError:
                raise TecplotLogicError(
                    'A facemap must first be created with Facemap.alloc()')

        @lock()
        def alloc(self, face_nodes, boundary_faces=0, boundary_connections=0):
            """Allocate space for the facemap.

            Parameters:
                face_nodes (`int`):  Total number of nodes for all faces. This is
                    not the number of unique nodes but the total number. For
                    example if a facemap defines two triangle polygons that share a
                    common face, ``faces`` would be 5 and ``face_nodes`` would be
                    6, not 4.
                boundary_faces (`int`, optional): Total number of boundary faces.
                    (default: 0)
                boundary_connections (`int`, optional): Total number of boundary
                    face elements or boundary face element/zone pairs. (default: 0)

            Returns:
                `Facemap`

            This is called when using the `Facemap.set_mapping()` method which is
            the preferred method for filling the connectivity of polytope zones. If
            the zone does not already have space allocated for a facemap and if you
            wish to use the `Facemap.set_nodes()` and `Facemap.set_elements()`
            methods to fill in the connectivity, then this must be called first::

                >>> facemap = zone.facemap.alloc(400, 25, 50)

            .. note:: Tecplot version 2017.2 or later.

                Setting the boundary faces and boundary connections using PyTecplot
                requires Tecplot version 2017.2 or later.
            """
            if __debug__:
                sdk_version_required = (2017, 2, 0)
                if (
                    version.sdk_version_info < sdk_version_required and
                    boundary_faces
                ):
                    msg = 'Creation of facemap boundary elements not supported.'
                    raise TecplotOutOfDateEngineError(sdk_version_required, msg)
            if not _tecutil.DataFaceMapAlloc(self.zone.index + 1,
                                             self.zone.num_faces, face_nodes,
                                             boundary_faces, boundary_connections):
                raise TecplotSystemError()
            c_void_p.__init__(self, self._native_reference())

        def __eq__(self, other):
            if not (self._has_data_backing and other._has_data_backing):
                return False
            self_addr = addressof(cast(self, POINTER(c_int64)).contents)
            other_addr = addressof(cast(other, POINTER(c_int64)).contents)
            return self_addr == other_addr

        def __ne__(self, other):
            return not (self == other)

        def __len__(self):
            return self.num_faces()

        def num_faces(self, element=None):
            """The number of faces of an element in this `Facemap`.

            Parameters:
                element (`int`, optional): Zero-based index of an
                    element. If no element is given, the total number of faces
                    in the map are returned.

            Returns:
                `int`

            Example usage::

                >>> print(zone.facemap.num_faces())
                1048576
            """
            if element is None:
                if not self._has_data_backing:
                    return self.zone.num_faces
                return _tecutil.DataFaceMapGetNFaces(self)
            else:
                return Elementmap(self.zone).num_faces(element)

        @property
        def num_unique_nodes(self):
            """`int`: The number of unique nodes in this `Facemap`.

            .. note:: This property is read-only.

            Example usage::

                >>> print(zone.facemap.num_unique_nodes)
                4194304
            """
            if not self._has_data_backing:
                return self.zone.num_points
            return _tecutil.DataFaceMapGetNumNodes(self)

        def num_nodes(self, face=None, element=None):
            """The number nodes for a given face in this `Facemap`.

            Parameters:
                face (`int`, required if no *element* is given): The
                    zero-based index of the face either within the given *element*
                    or globally. If no face is given, the number of nodes on the
                    given *element* are returned.
                element (`int`, required if no face is given): The
                    zero-based index of an element. If no element is given, then
                    *face* is globally indexed from zero over the whole zone.

            Returns:
                `int` The number of nodes.

            Example usage::

                >>> print(zone.facemap.num_nodes(face=1))
                4
            """
            if element is None:
                if face is None:
                    raise TecplotNotImplementedError
                    # While the following sum() is technically correct, it is
                    # extremely slow as currently, there is no way to query
                    # the total number of nodes in the connectivity list directly.
                    #
                    # return sum(self.num_nodes(i) for i in range(self.num_faces()))
                else:
                    if self.zone.zone_type == ZoneType.FEPolygon:
                        return 2
                    else:
                        return _tecutil.DataFaceMapGetNFaceNodes(self, face + 1)
            else:
                emap = Elementmap(self.zone)
                if face is None:
                    return sum(self.num_nodes(f) for f in emap.faces(element))
                else:
                    return self.num_nodes(emap.face(element, face))

        def num_boundary_connections(self, face=None, element=None):
            """The number of boundary connections for a given face.

            Parameters:
                face (`int`, required if no element is given): The
                    zero-based index of the face.
                element (`int`, optional): Zero-based index of an
                    element. If given, *face* will be locally indexed within this
                    element, otherwise *face* is globally indexed over the whole
                    zone.

            Returns:
                `int` The number of boundary connections.

            Example usage::

                >>> print(zone.facemap.num_boundary_connections(1))
                1
            """
            if face is None:
                if element is None:
                    raise TecplotNotImplementedError
                    # While the following sum() is technically correct using
                    # the face indices here, it is extremely slow as currently,
                    # there is no way to query the total number of boundary
                    # connections directly.
                    #
                    # faces = range(self.num_faces())
                else:
                    emap = Elementmap(self.zone)
                    faces = emap.faces(element)
                return sum(self.num_boundary_connections(f) for f in faces)
            elif element is not None:
                face = self.face(element, face)
            return _tecutil.DataFaceMapGetNBndryConns(self, face + 1)

        _BoundaryConnection = namedtuple('BoundaryConnection', ['element', 'zone'])

        def boundary_connection(self, face, offset, element=None):
            """The connected element and zone along a boundary face.

            Parameters:
                face (`int`): The zero-based index of the face.
                offset (`int`): The zero-based index of the node being
                    requested.
                element (`int`, optional): Zero-based index of an
                    element. If given, *face* will be locally indexed within this
                    element, otherwise *face* is globally indexed over the whole
                    zone.

            Returns:
                `namedtuple <collections.namedtuple>`: ``(element, zone)``:
                    ``element``
                        The zero-based index of the neighboring element.
                    ``zone``
                        The zone holding the neighboring element.

            Example usage::

                >>> bconn = zone.facemap.boundary_connection(0, 2)
                >>> print(bconn.element)
                128
                >>> print(bconn.zone.index)
                2
            """
            if element is not None:
                face = self.face(element, face)
            elem, zone = _tecutil.DataFaceMapGetBndryConn(self, face + 1,
                                                          offset + 1)
            return Facemap._BoundaryConnection(elem - 1,
                                               self.zone.dataset.zone(zone - 1))

        def node(self, face, offset, element=None):
            """The node index along a specific face.

            Parameters:
                face (`int`): The zero-based index of the face.
                offset (`int`): The zero-based index of the node being
                    requested.
                element (`int`, optional): Zero-based index of an
                    element. If given, *face* will be locally indexed within this
                    element, otherwise *face* is globally indexed over the whole
                    zone.

            Returns:
                `int` The node at the specified location.

            Example usage::

                >>> print(zone.facemap.face_node(0, 2))
                128
            """
            if element is not None:
                face = self.face(element, face)
            return _tecutil.DataFaceMapGetFaceNode(self, face + 1, offset + 1) - 1

        def left_element(self, face, element=None):
            """The element to the left of a specific face.

            Parameters:
                face (`int`): The zero-based index of the face.
                element (`int`, optional): Zero-based index of an
                    element. If given, *face* will be locally indexed within this
                    element, otherwise *face* is globally indexed over the whole
                    zone.

            Returns:
                `int`

            A negative number indicates there is no element to the left of this
            face. Example usage::

                >>> print(zone.facemap.left_element(0))
                128
            """
            if element is not None:
                face = self.face(element, face)
            return _tecutil.DataFaceMapGetLeftElem(self, face + 1) - 1

        def right_element(self, face, element=None):
            """The element to the right of a specific face.

            Parameters:
                face (`int`): The zero-based index of the face.
                element (`int`, optional): Zero-based index of an
                    element. If given, *face* will be locally indexed within this
                    element, otherwise *face* is globally indexed over the whole
                    zone.

            Returns:
                `int`

            A negative number indicates there is no element to the right of this
            face. Example usage::

                >>> print(zone.facemap.right_element(0))
                -1
            """
            if element is not None:
                face = self.face(element, face)
            return _tecutil.DataFaceMapGetRightElem(self, face + 1) - 1

        def face(self, element, offset):
            """Face index on a specific element.

            Parameters:
                element (`int`): The zero-based index of the element.
                offset (`int`): The zero-based index of the face being
                    requested.

            Returns:
                `int` Zero-based index of the face at the specified
                location.

            Example usage::

                >>> print(zone.facemap.face(0, 2))
                128
            """
            return Elementmap(self.zone).face(element, offset)

        @property
        def node_c_type(self):
            """The data type of the node indices.

            Possible values: `ctypes.c_int32`, `ctypes.c_int64`

            .. note:: This property is read-only.
            """
            _ctypes = {
                OffsetDataType.OffsetDataType_32Bit: c_int32,
                OffsetDataType.OffsetDataType_64Bit: c_int64}
            if self._has_data_backing:
                data_type = _tecutil.DataFaceMapGetNodeRawItemType(self)
                return _ctypes[data_type]

        @property
        def element_c_type(self):
            """The data type of the element indices.

            Possible values: `ctypes.c_int32`, `ctypes.c_int64`

            .. note:: This property is read-only.
            """
            _ctypes = {
                OffsetDataType.OffsetDataType_32Bit: c_int32,
                OffsetDataType.OffsetDataType_64Bit: c_int64}
            if self._has_data_backing:
                ref = self._native_reference(writable=True)
                data_type = _tecutil.DataFaceMapGetElementRawItemType(ref)
                return _ctypes[data_type]

        def set_nodes(self, facemap):
            """Sets the polytope connectivity.

            Parameters:
                facemap (2D array of zero-based `integers <int>`): The `list` of
                    `lists <list>` which need not all be the same length, defining
                    the individual elements by their nodes.

            The facemap must first be allocated with `Facemap.alloc()` and must be
            called from within a `Facemap.assignment()` context and should be
            followed by a call to `Facemap.set_elements()` to complete the
            connectivity map information needed for rendering. It is recomended to
            use the `Facemap.set_mapping()` which does all the required
            book keeping.
            """
            _dispatch = {
                c_int32: _tecutil.DataFaceMapAssignNodes,
                c_int64: _tecutil.DataFaceMapAssignNodes64}
            data_type = self.node_c_type
            nfaces = len(facemap)
            nnodes = [len(n) for n in facemap]
            nfacenodes = sum(nnodes)
            if self.zone.zone_type is ZoneType.FEPolygon:
                faces = None
            else:
                faces = (c_int32 * nfaces)(*nnodes)
            nodes = (data_type * nfacenodes)(*[n + 1 for n in it.chain(*facemap)])

            if __debug__:
                if min(nodes) < 1 or self.num_unique_nodes < max(nodes):
                    raise TecplotIndexError
                log.debug(dedent('''\
                    Facemap set nodes:
                        faces({}): {}
                        nodes: {}''').format(nfaces, array_to_str(faces),
                                             array_to_str(nodes)))

            ref = self._native_reference(writable=True)
            _dispatch[data_type](ref, nfaces, faces, nodes)

        def set_elements(self, left_elements, right_elements):
            """Sets the polytope connectivity.

            Parameters:
                left_elements (array of zero-based `integers <int>`): This is an
                    array of the elements to the left of each face in the facemap
                    and must be the same length as the number of faces.
                right_elements (array of zero-based `integers <int>`): Same as
                    *left_elements* for the right side of each face.

            The facemap must first be allocated with `Facemap.alloc()` and must be
            called from within a `Facemap.assignment()` context and should follow a
            call to `Facemap.set_nodes()` to complete the connectivity map
            information needed for rendering. Using `Facemap.set_mapping()` is
            recommended, which does all the required book keeping.
            """
            _dispatch = {
                c_int32: _tecutil.DataFaceMapAssignElems,
                c_int64: _tecutil.DataFaceMapAssignElems64}
            data_type = self.element_c_type
            nelements = len(left_elements)
            left = (data_type * nelements)(*[e + 1 for e in left_elements])
            right = (data_type * nelements)(*[e + 1 for e in right_elements])

            if __debug__:
                if self.zone.num_elements < max(left):
                    raise TecplotIndexError
                if self.zone.num_elements < max(right):
                    raise TecplotIndexError
                log.debug(dedent('''\
                    Facemap set elements:
                        left({}): {}
                        right({}): {}''').format(len(left), array_to_str(left),
                                                 len(right), array_to_str(right)))

            ref = self._native_reference(writable=True)
            _dispatch[data_type](ref, nelements, left, right)

        def set_boundary_connections(self, elements, zones):
            """Set the boundary connections.

            Parameters:
                elements (2D array of `integers <int>`): Zero-based indices of the
                    connected elements. This is a "ragged" array of dimension
                    :math:`(N,E_i)` where :math:`N` is the number of boundary faces
                    and :math:`E_i` is the number of boundary connected elements
                    for the :math:`i^{th}` face.
                zones (2D array of `integers <int>`): Zero-based indices of the
                    zones for each entry given in *elements*. This must be the same
                    shape as *elements*.

            The facemap must first be allocated with `Facemap.alloc()` and must be
            called from within a `Facemap.assignment()` context, and should follow
            calls to `Facemap.set_nodes()` and `Facemap.set_elements()` to complete
            the connectivity map information needed for rendering. Using
            `Facemap.set_mapping()` is recommended, which does all the required
            book keeping.

            """
            _dispatch = {
                c_int32: _tecutil.DataFaceMapAssignBConns,
                c_int64: _tecutil.DataFaceMapAssignBConns64}
            data_type = self.node_c_type
            nbfaces = len(elements)
            nbconns = (c_int32 * nbfaces)(*[len(n) for n in elements])
            nbelems = sum(nbconns)

            def _flatten_add_one(arr):
                def _f(x):
                    return x + 1 if x is not None else 0
                return list(map(_f, it.chain(*arr)))

            def _flatten_add_one_index(arr):
                def _f(x):
                    return getattr(x, 'index', x) + 1 if x is not None else 0
                return list(map(_f, it.chain(*arr)))

            belems = (data_type * nbelems)(*_flatten_add_one(elements))
            bzones = (c_int32 * nbelems)(*_flatten_add_one_index(zones))

            if __debug__:
                if min(belems) < 1 or self.zone.num_elements < max(belems):
                    raise TecplotIndexError
                if min(bzones) < 1 or self.zone.dataset.num_zones < max(bzones):
                    raise TecplotIndexError
                log.debug(dedent('''\
                    Facemap set boundary connections:
                        num boundary faces: {}
                        num boundary conns: {}
                        boundary elems: {}
                        boundary zones: {}'''.format(nbfaces,
                                                     array_to_str(nbconns),
                                                     array_to_str(belems),
                                                     array_to_str(bzones))))

            ref = self._native_reference(writable=True)
            _dispatch[data_type](ref, nbfaces, nbconns, belems, bzones)

        def set_mapping(self, facemap, elements, boundary_elements=None,
                        boundary_zones=None):
            """Set the node and element connectivity for this polytope zone.

            Parameters:
                facemap (2D array of zero-based `integers <int>`): The `list` of
                    `lists <list>` which need not all be the same length, defining
                    the individual elements by their nodes.
                elements (2D array of zero-based `integers <int>`): This is a
                    :math:`(2,N)` array where :math:`N` is the number of faces and
                    the items are a list of the elements to the left and right of
                    the face respectively.
                boundary_elements (2D array of `integers <int>`, optional):
                    Zero-based indices of the connected elements. This is a
                    "ragged" array of dimension :math:`(N, E_i)` where :math:`N` is
                    the number of boundary faces and :math:`E_i` is the number of
                    boundary connected elements for the :math:`i^{th}` face.
                boundary_zones (2D array of `integers <int>`, optional): Zero-based
                    indices of the zones for each entry given in *elements*. This
                    must be the same shape as *elements*.

            See the code example for the `Facemap` class object for details.
            """
            nfacenodes = sum(len(n) for n in facemap)
            if boundary_elements is not None:
                nbfaces = len(boundary_elements)
                nbelems = sum(len(n) for n in boundary_elements)
            else:
                nbfaces = 0
                nbelems = 0

            if __debug__:
                log.debug(dedent('''\
                    Facemap alloc:
                        num face nodes: {}
                        num boundary faces: {}
                        num boundary elements: {}'''.format(nfacenodes, nbfaces,
                                                            nbelems)))

            self.alloc(nfacenodes, nbfaces, nbelems)
            with self.assignment():
                self.set_nodes(facemap)
                self.set_elements(*elements)
                if boundary_elements is not None:
                    self.set_boundary_connections(boundary_elements, boundary_zones)

        @contextmanager
        def assignment(self):
            """Context manager for assigning facemap connections.

            This context ensures the proper book keeping is done when setting the
            connectivity list and must be used with `Facemap.set_nodes()`,
            `Facemap.set_elements()` and `Facemap.set_boundary_connections()`,
            which are used to define the connectivity of the zone.
            """
            if hasattr(self, '_in_assignment'):
                yield
            else:
                with lock():
                    ref = self._native_reference(writable=True)
                    _tecutil.DataFaceMapBeginAssign(ref)
                    try:
                        self._in_assignment = True
                        yield
                    finally:
                        try:
                            if not _tecutil.DataFaceMapEndAssign(ref):
                                raise TecplotSystemError()
                        finally:
                            del self._in_assignment

        def set_elementmap(self, elementmap):
            """Define connectivity per element.

            Parameters:
                elementmap (`integers <int>`): Zero-based indices of the nodes that
                    make up each face of each element. For polygons, the map is a
                    list of elements, each made of up a list of nodes. For
                    polyhedrons, this is a list of elements, made up a list of
                    faces, each made up of a list of nodes.

            .. warning::

                This method is mutually exclusive with the `Facemap.set_mapping()`
                and `Facemap.assignment()`, `Facemap.set_nodes()`,
                `Facemap.set_elements()` and `Facemap.set_boundary_connections()`
                family of methods. The size of the underlying arrays are calculated
                based on the elementmap given and a call to `Facemap.alloc()` is
                made which will override any previous allocation.

            This may be a more convenient way to describe the connectivity of a
            polytope zone, however it does not support boundary face connections to
            other zones (see `Facemap.set_mapping()`).

            Here is an example of an elementmap for two triangles (polygons)::

                nodes = ((0, 0, 0  ),
                         (1, 0, 0.5),
                         (0, 1, 0.5),
                         (1, 1, 1  ))
                elementmap = ((0, 1, 2),  # polygon 0, 3 faces
                              (1, 3, 2))  # polygon 1, 3 faces

            This is an example of an elementmap for two tetrahedrons
            (polyhedrons)::

                nodes = ((0, 0, 0),
                         (1, 1, 0),
                         (1, 0, 1),
                         (0, 1, 1),
                         (0, 0, 1))
                elementmap = (((0, 1, 2),  # polyhedron 0, 4 faces
                               (0, 1, 3),
                               (1, 3, 2),
                               (0, 2, 3)),
                              ((0, 2, 3),  # polyhedron 1, 4 faces
                               (2, 3, 4),
                               (0, 2, 4),
                               (0, 4, 3)))

            """
            _dispatch = {
                c_int32: _tecutil.DataFaceMapAssignElemToNodeMap,
                c_int64: _tecutil.DataFaceMapAssignElemToNodeMap64}
            data_type = self.node_c_type
            if data_type is None:
                data_type = c_int32

            nelems = len(elementmap)

            faces = [len(e) for e in elementmap]
            faces = (c_int32 * len(faces))(*faces)

            if self.zone.zone_type == ZoneType.FEPolygon:
                nodes = None
                elemmap = [n + 1 for face in elementmap for n in face]
            else:  # if self.zone.zone_type == ZoneType.FEPolyhedron:
                nodes = [len(face) for elem in elementmap for face in elem]
                nodes = (c_int32 * len(nodes))(*nodes)
                elemmap = [n + 1 for elem in elementmap
                           for face in elem
                           for n in face]

            elemmap = (data_type * len(elemmap))(*elemmap)

            if __debug__:
                if len(elemmap):
                    if min(elemmap) < 1 or self.num_unique_nodes < max(elemmap):
                        raise TecplotIndexError
                log.debug(dedent('''\
                    FaceMapAssign:
                        nelems: {}
                        faces: {}
                        nodes: {}
                        elemmap: {}''').format(nelems, array_to_str(faces),
                                               array_to_str(nodes),
                                               array_to_str(elemmap)))

            # reset facemap before we fill it with the nodemap
            if self.zone.zone_type == ZoneType.FEPolygon:
                self.alloc(int(self.zone.num_faces * self.num_nodes(0)))
            else:
                self.alloc(0)

            with lock():
                ref = self._native_reference(writable=True)
                if not _dispatch[data_type](ref, nelems, faces, nodes, elemmap):
                    raise TecplotSystemError()