def generateElements(cls,
                         fieldmodule,
                         coordinates,
                         nodeId,
                         startElementIdentifier=1):
        """
        Create valve elements from nodes.
        :param fieldmodule: Zinc fieldmodule to create elements in.
        :param coordinates: Coordinate field to define.
        :param nodeId: Node identifiers returned by generateNodes().
        Indexed by [n3=wall][n2=inlet->outlet][n1=around].
        :param startElementIdentifier: First element identifier to use.
        :return: next elementIdentifier, elementId[e1].
         """
        elementIdentifier = startElementIdentifier
        elementId = []
        elementsCountAround = 6  # fixed
        useCrossDerivatives = False

        mesh = fieldmodule.findMeshByDimension(3)
        tricubichermite = eftfactory_tricubichermite(mesh, useCrossDerivatives)
        eft = tricubichermite.createEftNoCrossDerivatives()
        setEftScaleFactorIds(eft, [1], [])
        scalefactors = [-1.0]
        tricubichermite.setEftLinearDerivative(eft, [3, 7],
                                               Node.VALUE_LABEL_D_DS3, 3, 7, 1)
        tricubichermite.setEftLinearDerivative(eft, [4, 8],
                                               Node.VALUE_LABEL_D_DS3, 4, 8, 1)

        elementtemplate = mesh.createElementtemplate()
        elementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        elementtemplate.defineField(coordinates, -1, eft)

        for ea in range(elementsCountAround):
            eb = (ea + 1) % elementsCountAround
            nids = [
                nodeId[0][0][ea], nodeId[0][0][eb], nodeId[0][1][ea],
                nodeId[0][1][eb], nodeId[1][0][ea], nodeId[1][0][eb],
                nodeId[1][1][ea], nodeId[1][1][eb]
            ]
            element = mesh.createElement(elementIdentifier, elementtemplate)
            element.setNodesByIdentifier(eft, nids)
            element.setScaleFactors(eft, scalefactors)
            elementId.append(elementIdentifier)
            elementIdentifier += 1

        return elementIdentifier, elementId
Beispiel #2
0
    def generateBaseMesh(cls, region, options):
        """
        Generate the base tricubic Hermite or bicubic Hermite linear mesh.
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: [] empty list of AnnotationGroup
        """
        elementsCountAround = options['Number of elements around']
        elementsCountUp = options['Number of elements up']
        elementsCountThroughWall = options['Number of elements through wall']
        useCrossDerivatives = options['Use cross derivatives']
        useCubicHermiteThroughWall = not (options['Use linear through wall'])
        excludeBottomRows = options['Exclude bottom rows']
        excludeTopRows = options['Exclude top rows']
        wallThickness = options['Wall thickness']
        wallThicknessRatioApex = options['Wall thickness ratio apex']
        lengthRatio = options['Length ratio']
        elementLengthRatioEquatorApex = options[
            'Element length ratio equator/apex']

        fm = region.getFieldmodule()
        fm.beginChange()
        coordinates = findOrCreateFieldCoordinates(fm)

        nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
        nodetemplateApex = nodes.createNodetemplate()
        nodetemplateApex.defineField(coordinates)
        nodetemplateApex.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_VALUE, 1)
        nodetemplateApex.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1)
        nodetemplateApex.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1)
        if useCubicHermiteThroughWall:
            nodetemplateApex.setValueNumberOfVersions(coordinates, -1,
                                                      Node.VALUE_LABEL_D_DS3,
                                                      1)
        if useCrossDerivatives:
            nodetemplate = nodes.createNodetemplate()
            nodetemplate.defineField(coordinates)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_VALUE, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS1DS2,
                                                  1)
            if useCubicHermiteThroughWall:
                nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                      Node.VALUE_LABEL_D_DS3,
                                                      1)
                nodetemplate.setValueNumberOfVersions(
                    coordinates, -1, Node.VALUE_LABEL_D2_DS1DS3, 1)
                nodetemplate.setValueNumberOfVersions(
                    coordinates, -1, Node.VALUE_LABEL_D2_DS2DS3, 1)
                nodetemplate.setValueNumberOfVersions(
                    coordinates, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1)
        else:
            nodetemplate = nodetemplateApex

        mesh = fm.findMeshByDimension(3)

        if useCubicHermiteThroughWall:
            eftfactory = eftfactory_tricubichermite(mesh, useCrossDerivatives)
        else:
            eftfactory = eftfactory_bicubichermitelinear(
                mesh, useCrossDerivatives)
        eft = eftfactory.createEftBasic()

        elementtemplate = mesh.createElementtemplate()
        elementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        elementtemplate.defineField(coordinates, -1, eft)

        cache = fm.createFieldcache()

        # create nodes
        nodeIdentifier = 1
        radiansPerElementAround = 2.0 * math.pi / elementsCountAround
        x = [0.0, 0.0, 0.0]
        dx_ds1 = [0.0, 0.0, 0.0]
        dx_ds2 = [0.0, 0.0, 0.0]
        dx_ds3 = [0.0, 0.0, 0.0]
        zero = [0.0, 0.0, 0.0]

        # pre-calculate positions and tangent/normal vectors up (elementsCountUp + 1) node layers
        outerWidth = 0.5
        outerLength = outerWidth * lengthRatio
        bOuter = 2.0 / (1.0 + elementLengthRatioEquatorApex / lengthRatio)
        aOuter = 1.0 - bOuter

        innerWidth = outerWidth - wallThickness
        innerLength = outerLength - wallThickness * wallThicknessRatioApex
        lengthRatioInner = innerLength / innerWidth if (
            innerWidth > 0.0) else lengthRatio
        bInner = 2.0 / (1.0 + elementLengthRatioEquatorApex / lengthRatio)
        aInner = 1.0 - bInner

        positionOuterArray = [(0, 0)] * (elementsCountUp + 1)
        positionInnerArray = [(0, 0)] * (elementsCountUp + 1)
        radiansUpOuterArray = [0] * (elementsCountUp + 1)
        radiansUpInnerArray = [0] * (elementsCountUp + 1)
        vector2OuterArray = [(0, 0)] * (elementsCountUp + 1)
        vector2InnerArray = [(0, 0)] * (elementsCountUp + 1)
        for n2 in range(elementsCountUp + 1):
            if n2 * 2 <= elementsCountUp:
                xi = n2 * 2 / elementsCountUp
            else:
                xi = 2.0 - (n2 * 2 / elementsCountUp)

            nxiOuter = aOuter * xi * xi + bOuter * xi
            dnxiOuter = 2.0 * aOuter * xi + bOuter
            radiansUpOuterArray[
                n2] = radiansUpOuter = nxiOuter * math.pi * 0.5 if (
                    n2 * 2 <= elementsCountUp) else (math.pi -
                                                     nxiOuter * math.pi * 0.5)
            dRadiansUpOuter = dnxiOuter * math.pi / elementsCountUp
            cosRadiansUpOuter = math.cos(radiansUpOuter)
            sinRadiansUpOuter = math.sin(radiansUpOuter)
            positionOuterArray[n2] = positionOuter = (outerWidth *
                                                      sinRadiansUpOuter,
                                                      -outerLength *
                                                      cosRadiansUpOuter)
            vector2OuterArray[n2] = (outerWidth * cosRadiansUpOuter *
                                     dRadiansUpOuter, outerLength *
                                     sinRadiansUpOuter * dRadiansUpOuter)

            nxiInner = aInner * xi * xi + bInner * xi
            dnxiInner = 2.0 * aInner * xi + bInner
            radiansUpInnerArray[
                n2] = radiansUpInner = nxiInner * math.pi * 0.5 if (
                    n2 * 2 <= elementsCountUp) else (math.pi -
                                                     nxiInner * math.pi * 0.5)
            dRadiansUpInner = dnxiInner * math.pi / elementsCountUp
            cosRadiansUpInner = math.cos(radiansUpInner)
            sinRadiansUpInner = math.sin(radiansUpInner)
            positionInnerArray[n2] = positionInner = (innerWidth *
                                                      sinRadiansUpInner,
                                                      -innerLength *
                                                      cosRadiansUpInner)
            vector2InnerArray[n2] = (innerWidth * cosRadiansUpInner *
                                     dRadiansUpInner, innerLength *
                                     sinRadiansUpInner * dRadiansUpInner)

        # now create the nodes
        for n3 in range(elementsCountThroughWall + 1):

            n3_fraction = n3 / elementsCountThroughWall

            for n2 in range(excludeBottomRows,
                            elementsCountUp + 1 - excludeTopRows):

                positionOuter = positionOuterArray[n2]
                positionInner = positionInnerArray[n2]
                position = (positionOuter[0] * n3_fraction + positionInner[0] *
                            (1.0 - n3_fraction),
                            positionOuter[1] * n3_fraction + positionInner[1] *
                            (1.0 - n3_fraction))

                radiansUpOuter = radiansUpOuterArray[n2]
                sinRadiansUpOuter = math.sin(radiansUpOuter)
                radiansUpInner = radiansUpInnerArray[n2]
                sinRadiansUpInner = math.sin(radiansUpInner)

                vector2Outer = vector2OuterArray[n2]
                vector2Inner = vector2InnerArray[n2]
                vector2 = (vector2Outer[0] * n3_fraction + vector2Inner[0] *
                           (1.0 - n3_fraction), vector2Outer[1] * n3_fraction +
                           vector2Inner[1] * (1.0 - n3_fraction))

                vector3 = ((positionOuter[0] - positionInner[0]) /
                           elementsCountThroughWall,
                           (positionOuter[1] - positionInner[1]) /
                           elementsCountThroughWall)

                if n2 == 0:
                    # create bottom apex node
                    node = nodes.createNode(nodeIdentifier, nodetemplateApex)
                    cache.setNode(node)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_VALUE, 1,
                                                  [0.0, 0.0, position[1]])
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1,
                                                  [0.0, vector2[0], 0.0])
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1,
                                                  [vector2[0], 0.0, 0.0])
                    if useCubicHermiteThroughWall:
                        coordinates.setNodeParameters(cache, -1,
                                                      Node.VALUE_LABEL_D_DS3,
                                                      1,
                                                      [0.0, 0.0, vector3[1]])
                    nodeIdentifier = nodeIdentifier + 1

                elif n2 < elementsCountUp:
                    # create regular rows between apexes
                    for n1 in range(elementsCountAround):
                        radiansAround = n1 * radiansPerElementAround
                        cosRadiansAround = math.cos(radiansAround)
                        sinRadiansAround = math.sin(radiansAround)
                        x = [
                            position[0] * cosRadiansAround,
                            position[0] * sinRadiansAround, position[1]
                        ]
                        dx_ds1 = [
                            position[0] * -sinRadiansAround *
                            radiansPerElementAround, position[0] *
                            cosRadiansAround * radiansPerElementAround, 0.0
                        ]
                        dx_ds2 = [
                            vector2[0] * cosRadiansAround,
                            vector2[0] * sinRadiansAround, vector2[1]
                        ]
                        dx_ds3 = [
                            vector3[0] * cosRadiansAround,
                            vector3[0] * sinRadiansAround, vector3[1]
                        ]
                        node = nodes.createNode(nodeIdentifier, nodetemplate)
                        cache.setNode(node)
                        coordinates.setNodeParameters(cache, -1,
                                                      Node.VALUE_LABEL_VALUE,
                                                      1, x)
                        coordinates.setNodeParameters(cache, -1,
                                                      Node.VALUE_LABEL_D_DS1,
                                                      1, dx_ds1)
                        coordinates.setNodeParameters(cache, -1,
                                                      Node.VALUE_LABEL_D_DS2,
                                                      1, dx_ds2)
                        if useCubicHermiteThroughWall:
                            coordinates.setNodeParameters(
                                cache, -1, Node.VALUE_LABEL_D_DS3, 1, dx_ds3)
                        if useCrossDerivatives:
                            coordinates.setNodeParameters(
                                cache, -1, Node.VALUE_LABEL_D2_DS1DS2, 1, zero)
                            if useCubicHermiteThroughWall:
                                coordinates.setNodeParameters(
                                    cache, -1, Node.VALUE_LABEL_D2_DS1DS3, 1,
                                    zero)
                                coordinates.setNodeParameters(
                                    cache, -1, Node.VALUE_LABEL_D2_DS2DS3, 1,
                                    zero)
                                coordinates.setNodeParameters(
                                    cache, -1, Node.VALUE_LABEL_D3_DS1DS2DS3,
                                    1, zero)
                        nodeIdentifier = nodeIdentifier + 1

                else:
                    # create top apex node
                    node = nodes.createNode(nodeIdentifier, nodetemplateApex)
                    cache.setNode(node)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_VALUE, 1,
                                                  [0.0, 0.0, position[1]])
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1,
                                                  [0.0, vector2[0], 0.0])
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1,
                                                  [-vector2[0], 0.0, 0.0])
                    if useCubicHermiteThroughWall:
                        coordinates.setNodeParameters(cache, -1,
                                                      Node.VALUE_LABEL_D_DS3,
                                                      1,
                                                      [0.0, 0.0, vector3[1]])
                    nodeIdentifier = nodeIdentifier + 1

        # create elements
        elementIdentifier = 1
        # now (node offset through wall) varies with number of excluded rows
        now = 0
        if excludeBottomRows == 0:
            now += 1  # bottom apex node
        if excludeTopRows == 0:
            now += 1  # top apex node
        fullNodeRows = elementsCountUp - 1
        if excludeBottomRows > 1:
            fullNodeRows -= (excludeBottomRows - 1)
        if excludeTopRows > 1:
            fullNodeRows -= (excludeTopRows - 1)
        if fullNodeRows > 0:
            now += fullNodeRows * elementsCountAround
        row2NodeOffset = 2 if (excludeBottomRows == 0) else 1
        for e3 in range(elementsCountThroughWall):
            no = e3 * now

            if excludeBottomRows == 0:
                # create bottom apex elements, editing eft scale factor identifiers around apex
                # scale factor identifiers follow convention of offsetting by 100 for each 'version'
                elementtemplate1 = mesh.createElementtemplate()
                elementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE)
                for e1 in range(elementsCountAround):
                    va = e1
                    vb = (e1 + 1) % elementsCountAround
                    eft1 = eftfactory.createEftShellPoleBottom(
                        va * 100, vb * 100)
                    elementtemplate1.defineField(coordinates, -1, eft1)
                    element = mesh.createElement(elementIdentifier,
                                                 elementtemplate1)
                    bni1 = no + 1
                    bni2 = no + e1 + 2
                    bni3 = no + (e1 + 1) % elementsCountAround + 2
                    nodeIdentifiers = [
                        bni1, bni2, bni3, bni1 + now, bni2 + now, bni3 + now
                    ]
                    element.setNodesByIdentifier(eft1, nodeIdentifiers)
                    # set general linear map coefficients
                    radiansAround = e1 * radiansPerElementAround
                    radiansAroundNext = (
                        (e1 + 1) %
                        elementsCountAround) * radiansPerElementAround
                    scalefactors = [
                        -1.0,
                        math.sin(radiansAround),
                        math.cos(radiansAround), radiansPerElementAround,
                        math.sin(radiansAroundNext),
                        math.cos(radiansAroundNext), radiansPerElementAround,
                        math.sin(radiansAround),
                        math.cos(radiansAround), radiansPerElementAround,
                        math.sin(radiansAroundNext),
                        math.cos(radiansAroundNext), radiansPerElementAround
                    ]
                    result = element.setScaleFactors(eft1, scalefactors)
                    elementIdentifier = elementIdentifier + 1

            # create regular rows between apexes
            rowLimit = (elementsCountUp - 2)
            if excludeBottomRows > 1:
                rowLimit -= (excludeBottomRows - 1)
            if excludeTopRows > 1:
                rowLimit -= (excludeTopRows - 1)
            for e2 in range(0, rowLimit):
                for e1 in range(elementsCountAround):
                    element = mesh.createElement(elementIdentifier,
                                                 elementtemplate)
                    bni11 = no + e2 * elementsCountAround + e1 + row2NodeOffset
                    bni12 = no + e2 * elementsCountAround + (
                        e1 + 1) % elementsCountAround + row2NodeOffset
                    bni21 = no + (
                        e2 + 1) * elementsCountAround + e1 + row2NodeOffset
                    bni22 = no + (e2 + 1) * elementsCountAround + (
                        e1 + 1) % elementsCountAround + row2NodeOffset
                    nodeIdentifiers = [
                        bni11, bni12, bni21, bni22, bni11 + now, bni12 + now,
                        bni21 + now, bni22 + now
                    ]
                    result = element.setNodesByIdentifier(eft, nodeIdentifiers)
                    elementIdentifier = elementIdentifier + 1

            if excludeTopRows == 0:
                # create top apex elements, editing eft scale factor identifiers around apex
                # scale factor identifiers follow convention of offsetting by 100 for each 'version'
                elementtemplate1 = mesh.createElementtemplate()
                elementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE)
                for e1 in range(elementsCountAround):
                    va = e1
                    vb = (e1 + 1) % elementsCountAround
                    eft1 = eftfactory.createEftShellPoleTop(va * 100, vb * 100)
                    elementtemplate1.defineField(coordinates, -1, eft1)
                    element = mesh.createElement(elementIdentifier,
                                                 elementtemplate1)
                    bni3 = no + now
                    bni1 = bni3 - elementsCountAround + e1
                    bni2 = bni3 - elementsCountAround + (
                        e1 + 1) % elementsCountAround
                    nodeIdentifiers = [
                        bni1, bni2, bni3, bni1 + now, bni2 + now, bni3 + now
                    ]
                    element.setNodesByIdentifier(eft1, nodeIdentifiers)
                    # set general linear map coefficients
                    radiansAround = math.pi + e1 * radiansPerElementAround
                    radiansAroundNext = math.pi + (
                        (e1 + 1) %
                        elementsCountAround) * radiansPerElementAround
                    scalefactors = [
                        -1.0, -math.sin(radiansAround),
                        math.cos(radiansAround), radiansPerElementAround,
                        -math.sin(radiansAroundNext),
                        math.cos(radiansAroundNext), radiansPerElementAround,
                        -math.sin(radiansAround),
                        math.cos(radiansAround), radiansPerElementAround,
                        -math.sin(radiansAroundNext),
                        math.cos(radiansAroundNext), radiansPerElementAround
                    ]
                    result = element.setScaleFactors(eft1, scalefactors)
                    elementIdentifier = elementIdentifier + 1

        if False:  # (wallThickness < 0.5):  # and (wallThicknessRatioApex != 1.0):
            r = fm.createFieldMagnitude(coordinates)
            const05 = fm.createFieldConstant([0.5])
            d = fm.createFieldSubtract(const05, r)
            d_r = fm.createFieldDivide(d, r)
            rRatio = 1.0 - wallThicknessRatioApex
            rScale = fm.createFieldConstant([rRatio])
            zScale = fm.createFieldMultiply(d_r, rScale)
            one = fm.createFieldConstant([1.0])
            one_plus_zScale = fm.createFieldAdd(one, zScale)
            scale = fm.createFieldConcatenate([one, one, one_plus_zScale])
            newCoordinates = fm.createFieldMultiply(coordinates, scale)
            fieldassignment = coordinates.createFieldassignment(newCoordinates)
            fieldassignment.assign()

        fm.endChange()
        return []
Beispiel #3
0
def createAnnulusMesh3d(nodes,
                        mesh,
                        nextNodeIdentifier,
                        nextElementIdentifier,
                        startPointsx,
                        startPointsd1,
                        startPointsd2,
                        startPointsd3,
                        startNodeId,
                        startDerivativesMap,
                        endPointsx,
                        endPointsd1,
                        endPointsd2,
                        endPointsd3,
                        endNodeId,
                        endDerivativesMap,
                        forceStartLinearXi3=False,
                        forceMidLinearXi3=False,
                        forceEndLinearXi3=False,
                        maxStartThickness=None,
                        maxEndThickness=None,
                        useCrossDerivatives=False,
                        elementsCountRadial=1,
                        meshGroups=None,
                        wallAnnotationGroups=None,
                        tracksurface=None,
                        startProportions=None,
                        endProportions=None,
                        rescaleStartDerivatives=False,
                        rescaleEndDerivatives=False,
                        sampleBlend=0.0):
    """
    Create an annulus mesh from a loop of start points/nodes with specified derivative mappings to
    a loop of end points/nodes with specified derivative mappings.
    Derivative d3 is through the wall. Currently limited to single element layer through wall.
    Points/nodes order cycles fastest around the annulus, then through the wall.
    Note doesn't support cross derivatives.
    Arrays are indexed by n3 (node through wall, size 2), n2 (node along/radial), n1 (node around, variable size)
    and coordinate component c.
    :param nodes: The nodeset to create nodes in.
    :param mesh: The mesh to create elements in.
    :param nextNodeIdentifier, nextElementIdentifier: Next identifiers to use and increment.
    :param startPointsx, startPointsd1, startPointsd2, startPointsd3, endPointsx, endPointsd1, endPointsd2, endPointsd3:
        List array[n3][n1][c] or start/point coordinates and derivatives. To linearise through the wall, pass None to
        d3. If both ends are linear through the wall, interior points are linear through the wall.
    :param startNodeId, endNodeId: List array [n3][n1] of existing node identifiers to use at start/end. Pass None for
        argument if no nodes are specified at end. These arguments are 'all or nothing'.
    :param startDerivativesMap, endDerivativesMap: List array[n3][n1] of mappings for d/dxi1, d/dxi2, d/dxi3 at
        start/end of form:
        ( (1, -1, 0), (1, 0, 0), None ) where the first tuple means d/dxi1 = d/ds1 - d/ds2. Only 0, 1 and -1 may be
        used.
        None means use default e.g. d/dxi2 = d/ds2.
        Pass None for the entire argument to use the defaults d/dxi1 = d/ds1, d/dxi2 = d/ds2, d/dxi3 = d/ds3.
        Pass a 4th mapping to apply to d/dxi1 on other side of node; if not supplied first mapping applies both sides.
    :param forceStartLinearXi3, forceMidLinearXi3, forceEndLinearXi3: Force start, middle or
        end elements to be linear through the wall, even if d3 is supplied at either end.
        Can only use forceMidLinearXi3 only if at least one end is linear in d3.
    :param maxStartThickness, maxEndThickness: Optional maximum override on start/end thicknesses.
    :param useCrossDerivatives: May only be True if no derivatives maps are in use.
    :param elementsCountRadial: Optional number of elements in radial direction between start and end.
    :param meshGroups:  Optional sequence of Zinc MeshGroup for adding all new elements to, or a sequence of
    length elementsCountRadial containing sequences of mesh groups to add rows of radial elements to
    from start to end.
    :param wallAnnotationGroups: Annotation groups for adding all new elements to a sequence
    of groups to add to elements through wall.
    :param tracksurface: Description for outer surface representation used for creating annulus mesh. Provides
    information for creating radial nodes on annulus that sit on tracksurface. Need startProportions and endProportions
    to work.
    :param startProportions: Proportion around and along of startPoints on tracksurface. These vary with nodes
    around as for startPoints. Values only given for tracksurface for outer layer (xi3 == 1).
    :param endProportions: Proportion around and along of endPoints on track surface. These vary with nodes
    around as for endPoints. Values only given for tracksurface for outer layer (xi3 == 1).
    :param rescaleStartDerivatives, rescaleEndDerivatives: Optional flags to compute and multiply additional scale
    factors on start, end or both radial derivatives to fit arc length, needed if derivatives are of the wrong scale
    for the radial distances and the chosen elementsCountRadial. If either is True, derivatives and sampled radial
    nodes are spaced for a gradual change of derivative from that at the other end. If both are True, scaling is set to
    give even sampling and arclength derivatives.
    :param sampleBlend: Real value varying from 0.0 to 1.0 controlling weighting of start and end
    derivatives when interpolating extra points in-between, where 0.0 = sample with equal end derivatives,
    and 1.0 = proportional to current magnitudes, interpolated in between.
    :return: Final values of nextNodeIdentifier, nextElementIdentifier
    """
    assert (elementsCountRadial >=
            1), 'createAnnulusMesh3d:  Invalid number of radial elements'
    startLinearXi3 = (not startPointsd3) or forceStartLinearXi3
    endLinearXi3 = (not endPointsd3) or forceEndLinearXi3
    midLinearXi3 = (startLinearXi3 and endLinearXi3) or (
        (startLinearXi3 or endLinearXi3) and forceMidLinearXi3)
    # get list whether each row of nodes in elements is linear in Xi3
    # this is for element use; start/end nodes may have d3 even if element is linear
    rowLinearXi3 = [
        startLinearXi3
    ] + [midLinearXi3] * (elementsCountRadial - 1) + [endLinearXi3]
    assert (not useCrossDerivatives) or ((not startDerivativesMap) and (not endDerivativesMap)), \
        'createAnnulusMesh3d:  Cannot use cross derivatives with derivatives map'
    nodesCountWall = len(startPointsx)
    assert (len(startPointsd1) == nodesCountWall) and (len(startPointsd2) == nodesCountWall) and \
           (startLinearXi3 or (len(startPointsd3) == nodesCountWall)) and \
           (len(endPointsx) == nodesCountWall) and (len(endPointsd1) == nodesCountWall) and \
           (len(endPointsd2) == nodesCountWall) and (endLinearXi3 or (len(endPointsd3) == nodesCountWall)) and \
           ((startNodeId is None) or (len(startNodeId) == nodesCountWall)) and \
           ((endNodeId is None) or (len(endNodeId) == nodesCountWall)) and \
           ((startDerivativesMap is None) or (len(startDerivativesMap) == nodesCountWall)) and \
           ((endDerivativesMap is None) or (len(endDerivativesMap) == nodesCountWall)),\
        'createAnnulusMesh3d:  Mismatch in number of layers through wall'
    elementsCountAround = nodesCountAround = len(startPointsx[0])
    assert (
        nodesCountAround > 1
    ), 'createAnnulusMesh3d:  Invalid number of points/nodes around annulus'
    for n3 in range(nodesCountWall):
        assert (len(startPointsx[n3]) == nodesCountAround) and (len(startPointsd1[n3]) == nodesCountAround) and \
               (len(startPointsd2[n3]) == nodesCountAround) and \
               (startLinearXi3 or (len(startPointsd3[n3]) == nodesCountAround)) and\
               (len(endPointsx[n3]) == nodesCountAround) and (len(endPointsd1[n3]) == nodesCountAround) and \
               (len(endPointsd2[n3]) == nodesCountAround) and \
               (endLinearXi3 or (len(endPointsd3[n3]) == nodesCountAround)) and \
               ((startNodeId is None) or (len(startNodeId[n3]) == nodesCountAround)) and\
               ((endNodeId is None) or (len(endNodeId[n3]) == nodesCountAround)) and \
               ((startDerivativesMap is None) or (len(startDerivativesMap[n3]) == nodesCountAround)) and \
               ((endDerivativesMap is None) or (len(endDerivativesMap[n3]) == nodesCountAround)), \
            'createAnnulusMesh3d:  Mismatch in number of points/nodes in layers through wall'
    rowMeshGroups = meshGroups
    if meshGroups:
        assert isinstance(
            meshGroups,
            Sequence), 'createAnnulusMesh3d:  Mesh groups is not a sequence'
        if (len(meshGroups) == 0) or (not isinstance(meshGroups[0], Sequence)):
            rowMeshGroups = [meshGroups] * elementsCountRadial
        else:
            assert len(meshGroups) == elementsCountRadial, \
                'createAnnulusMesh3d:  Length of meshGroups sequence does not equal elementsCountRadial'
    if wallAnnotationGroups:
        assert len(wallAnnotationGroups) == nodesCountWall - 1, \
            'createAnnulusMesh3d:  Length of wallAnnotationGroups sequence does not equal elementsCountThroughWall'
    if tracksurface:
        assert startProportions and endProportions, \
            'createAnnulusMesh3d: Missing start and/or end proportions for use with tracksurface'
        assert len(startProportions) == nodesCountAround, \
            'createAnnulusMesh3d: Length of startProportions does not equal nodesCountAround'
        assert len(endProportions) == nodesCountAround, \
            'createAnnulusMesh3d: Length of endProportions does not equal nodesCountAround'

    fm = mesh.getFieldmodule()
    fm.beginChange()
    cache = fm.createFieldcache()
    coordinates = findOrCreateFieldCoordinates(fm)

    # Build arrays of points from start to end
    px = [[] for n3 in range(nodesCountWall)]
    pd1 = [[] for n3 in range(nodesCountWall)]
    pd2 = [[] for n3 in range(nodesCountWall)]
    pd3 = [[] for n3 in range(nodesCountWall)]

    # Find total wall thickness
    thicknessProportions = []
    thicknesses = []
    thicknesses.append([
        vector.magnitude([
            (startPointsx[nodesCountWall - 1][n1][c] - startPointsx[0][n1][c])
            for c in range(3)
        ]) for n1 in range(nodesCountAround)
    ])
    for n2 in range(1, elementsCountRadial):
        thicknesses.append([None] * nodesCountAround)
    thicknesses.append([
        vector.magnitude([
            (endPointsx[nodesCountWall - 1][n1][c] - endPointsx[0][n1][c])
            for c in range(3)
        ]) for n1 in range(nodesCountAround)
    ])

    for n3 in range(nodesCountWall):
        px[n3] = [startPointsx[n3], endPointsx[n3]]
        pd1[n3] = [startPointsd1[n3], endPointsd1[n3]]
        pd2[n3] = [startPointsd2[n3], endPointsd2[n3]]
        pd3[n3] = [
            startPointsd3[n3] if (startPointsd3 is not None) else None,
            endPointsd3[n3] if (endPointsd3 is not None) else None
        ]

        startThicknessList = \
            [vector.magnitude([(startPointsx[n3][n1][c] - startPointsx[n3 - (1 if n3 > 0 else 0)][n1][c])
                               for c in range(3)]) for n1 in range(len(startPointsx[n3]))]
        endThicknessList = \
            [vector.magnitude([(endPointsx[n3][n1][c] - endPointsx[n3 - (1 if n3 > 0 else 0)][n1][c])
                               for c in range(3)]) for n1 in range(len(endPointsx[n3]))]
        thicknessList = [startThicknessList,
                         endThicknessList]  # thickness of each layer

        startThicknessProportions = [
            thicknessList[0][c] / thicknesses[0][c]
            for c in range(nodesCountAround)
        ]
        endThicknessProportions = [
            thicknessList[1][c] / thicknesses[-1][c]
            for c in range(nodesCountAround)
        ]
        thicknessProportions.append(
            [startThicknessProportions, endThicknessProportions])

    if rescaleStartDerivatives:
        scaleFactorMapStart = [[] for n3 in range(nodesCountWall)]
    if rescaleEndDerivatives:
        scaleFactorMapEnd = [[] for n3 in range(nodesCountWall)]

    # following code adds in-between points, but also handles rescaling for 1 radial element
    for n3 in range(nodesCountWall):
        for n2 in range(1, elementsCountRadial):
            px[n3].insert(n2, [None] * nodesCountAround)
            pd1[n3].insert(n2, [None] * nodesCountAround)
            pd2[n3].insert(n2, [None] * nodesCountAround)
            pd3[n3].insert(n2,
                           None if midLinearXi3 else [None] * nodesCountAround)
            thicknessProportions[n3].insert(n2, [None] * nodesCountAround)

    if maxStartThickness:
        for n1 in range(nodesCountAround):
            thicknesses[0][n1] = min(thicknesses[0][n1], maxStartThickness)
    if maxEndThickness:
        for n1 in range(nodesCountAround):
            thicknesses[-1][n1] = min(thicknesses[-1][n1], maxEndThickness)
    n3 = nodesCountWall - 1
    for n1 in range(nodesCountAround):
        ax = startPointsx[n3][n1]
        ad1, ad2 = getMappedD1D2(
            [startPointsd1[n3][n1], startPointsd2[n3][n1]] +
            ([startPointsd3[n3][n1]] if startPointsd3 else []),
            startDerivativesMap[n3][n1] if startDerivativesMap else None)
        bx = endPointsx[n3][n1]
        bd1, bd2 = getMappedD1D2(
            [endPointsd1[n3][n1], endPointsd2[n3][n1]] +
            ([endPointsd3[n3][n1]] if endPointsd3 else []),
            endDerivativesMap[n3][n1] if endDerivativesMap else None)

        # sample between start and end points and derivatives
        # scaling end derivatives to arc length gives even curvature along the curve
        aMag = vector.magnitude(ad2)
        bMag = vector.magnitude(bd2)
        ad2Scaled = vector.setMagnitude(
            ad2,
            0.5 * ((1.0 + sampleBlend) * aMag + (1.0 - sampleBlend) * bMag))
        bd2Scaled = vector.setMagnitude(
            bd2,
            0.5 * ((1.0 + sampleBlend) * bMag + (1.0 - sampleBlend) * aMag))
        scaling = interp.computeCubicHermiteDerivativeScaling(
            ax, ad2Scaled, bx, bd2Scaled)
        ad2Scaled = [d * scaling for d in ad2Scaled]
        bd2Scaled = [d * scaling for d in bd2Scaled]
        derivativeMagnitudeStart = None if rescaleStartDerivatives else vector.magnitude(
            ad2)
        derivativeMagnitudeEnd = None if rescaleEndDerivatives else vector.magnitude(
            bd2)
        if tracksurface:
            mx, md2, md1, md3, mProportions = \
                tracksurface.createHermiteCurvePoints(startProportions[n1][0], startProportions[n1][1],
                                                      endProportions[n1][0], endProportions[n1][1], elementsCountRadial,
                                                      derivativeStart=[d / elementsCountRadial for d in ad2Scaled],
                                                      derivativeEnd=[d / elementsCountRadial for d in bd2Scaled])
            mx, md2, md1 = \
                tracksurface.resampleHermiteCurvePointsSmooth(mx, md2, md1, md3, mProportions,
                                                              derivativeMagnitudeStart, derivativeMagnitudeEnd)[0:3]
            # interpolate thicknesses using xi calculated from radial arclength distances to points
            arcLengthInsideToRadialPoint = \
                [0.0] + [interp.getCubicHermiteArcLength(mx[n2], md2[n2], mx[n2 + 1], md2[n2 + 1])
                         for n2 in range(elementsCountRadial)]
            arclengthInsideToOutside = sum(arcLengthInsideToRadialPoint)
            thi = []
            for n2 in range(elementsCountRadial + 1):
                xi2 = arcLengthInsideToRadialPoint[
                    n2 - 1] / arclengthInsideToOutside
                thi.append(thicknesses[-1][n1] * xi2 + thicknesses[0][n1] *
                           (1.0 - xi2))
            thiProportion = []
            for m3 in range(nodesCountWall):
                thiProportionRadial = []
                for n2 in range(elementsCountRadial + 1):
                    xi2 = arcLengthInsideToRadialPoint[
                        n2 - 1] / arclengthInsideToOutside
                    thiProportionRadial.append(
                        thicknessProportions[m3][-1][n1] * xi2 +
                        thicknessProportions[m3][0][n1] * (1.0 - xi2))
                thiProportion.append(thiProportionRadial)
        else:
            mx, md2, me, mxi = interp.sampleCubicHermiteCurvesSmooth(
                [ax, bx], [ad2Scaled, bd2Scaled], elementsCountRadial,
                derivativeMagnitudeStart, derivativeMagnitudeEnd)[0:4]
            md1 = interp.interpolateSampleLinear([ad1, bd1], me, mxi)
            thi = interp.interpolateSampleLinear(
                [thicknesses[0][n1], thicknesses[-1][n1]], me, mxi)
            thiProportion = []
            for m3 in range(nodesCountWall):
                thiProportion.append(
                    interp.interpolateSampleLinear([
                        thicknessProportions[m3][0][n1],
                        thicknessProportions[m3][-1][n1]
                    ], me, mxi))

        # set scalefactors if rescaling, make same on inside for now
        if rescaleStartDerivatives:
            scaleFactor = vector.magnitude(md2[0]) / vector.magnitude(ad2)
            scaleFactorMapStart[n3].append(scaleFactor)
        if rescaleEndDerivatives:
            scaleFactor = vector.magnitude(md2[-1]) / vector.magnitude(bd2)
            scaleFactorMapEnd[n3].append(scaleFactor)

        for n2 in range(1, elementsCountRadial):
            px[n3][n2][n1] = mx[n2]
            pd1[n3][n2][n1] = md1[n2]
            pd2[n3][n2][n1] = md2[n2]
            thicknesses[n2][n1] = thi[n2]
            for m3 in range(nodesCountWall):
                thicknessProportions[m3][n2][n1] = thiProportion[m3][n2]

    xi3List = [[[[] for n1 in range(nodesCountAround)]
                for n2 in range(elementsCountRadial + 1)]
               for n3 in range(nodesCountWall)]
    for n1 in range(nodesCountAround):
        for n2 in range(elementsCountRadial + 1):
            xi3 = 0.0
            for n3 in range(nodesCountWall):
                xi3 += thicknessProportions[n3][n2][n1]
                xi3List[n3][n2][n1] = xi3

    # now get inner positions from normal and thickness, derivatives from curvature
    for n2 in range(1, elementsCountRadial):
        # first smooth derivative 1 around outer loop
        pd1[-1][n2] = \
            interp.smoothCubicHermiteDerivativesLoop(px[-1][n2], pd1[-1][n2],
                                                     magnitudeScalingMode=interp.DerivativeScalingMode.HARMONIC_MEAN)

        for n3 in range(0, nodesCountWall - 1):
            for n1 in range(nodesCountAround):
                xi3 = 1 - xi3List[n3][n2][n1]
                normal = vector.normalise(
                    vector.crossproduct3(pd1[-1][n2][n1], pd2[-1][n2][n1]))
                thickness = thicknesses[n2][n1] * xi3
                d3 = [d * thickness for d in normal]
                px[n3][n2][n1] = [(px[-1][n2][n1][c] - d3[c])
                                  for c in range(3)]
                # calculate inner d1 from curvature around
                n1m = n1 - 1
                n1p = (n1 + 1) % nodesCountAround
                curvature = 0.5 * (interp.getCubicHermiteCurvature(
                    px[-1][n2][n1m], pd1[-1][n2][n1m], px[-1][n2][n1], pd1[-1]
                    [n2][n1], normal, 1.0) + interp.getCubicHermiteCurvature(
                        px[-1][n2][n1], pd1[-1][n2][n1], px[-1][n2][n1p],
                        pd1[-1][n2][n1p], normal, 0.0))
                factor = 1.0 + curvature * thickness
                pd1[n3][n2][n1] = [factor * d for d in pd1[-1][n2][n1]]
                # calculate inner d2 from curvature radially
                n2m = n2 - 1
                n2p = n2 + 1
                curvature = 0.5 * (interp.getCubicHermiteCurvature(
                    px[-1][n2m][n1], pd2[-1][n2m][n1], px[-1][n2][n1], pd2[-1]
                    [n2][n1], normal, 1.0) + interp.getCubicHermiteCurvature(
                        px[-1][n2][n1], pd2[-1][n2][n1], px[-1][n2p][n1],
                        pd2[-1][n2p][n1], normal, 0.0))
                factor = 1.0 + curvature * thickness
                pd2[n3][n2][n1] = [factor * d for d in pd2[-1][n2][n1]]
                d2Scaled = [factor * d for d in pd2[-1][n2][n1]]
                if vector.dotproduct(vector.normalise(pd2[-1][n2][n1]),
                                     vector.normalise(d2Scaled)) == -1:
                    pd2[n3][n2][n1] = [-factor * d for d in pd2[-1][n2][n1]]
                if not midLinearXi3:
                    pd3[n3][n2][n1] = pd3[-1][n2][n1] = \
                        [d * thicknesses[n2][n1] * thicknessProportions[n3 + 1][n2][n1] for d in normal]

            # smooth derivative 1 around inner loop
            pd1[n3][n2] = interp.smoothCubicHermiteDerivativesLoop(
                px[n3][n2],
                pd1[n3][n2],
                magnitudeScalingMode=interp.DerivativeScalingMode.HARMONIC_MEAN
            )

    for n3 in range(0, nodesCountWall):
        # smooth derivative 2 radially/along annulus
        for n1 in range(nodesCountAround):
            mx = [px[n3][n2][n1] for n2 in range(elementsCountRadial + 1)]
            md2 = [pd2[n3][n2][n1] for n2 in range(elementsCountRadial + 1)]
            # replace mapped start/end d2
            md2[0] = getMappedD1D2(
                [startPointsd1[n3][n1], startPointsd2[n3][n1]] +
                ([startPointsd3[n3][n1]] if startPointsd3 else []),
                startDerivativesMap[n3][n1]
                if startDerivativesMap else None)[1]
            md2[-1] = getMappedD1D2(
                [endPointsd1[n3][n1], endPointsd2[n3][n1]] +
                ([endPointsd3[n3][n1]] if endPointsd3 else []),
                endDerivativesMap[n3][n1] if endDerivativesMap else None)[1]

            sd2 = interp.smoothCubicHermiteDerivativesLine(
                mx,
                md2,
                fixAllDirections=True,
                fixStartDerivative=not rescaleStartDerivatives,
                fixStartDirection=rescaleStartDerivatives,
                fixEndDerivative=not rescaleEndDerivatives,
                fixEndDirection=rescaleEndDerivatives,
                magnitudeScalingMode=interp.DerivativeScalingMode.HARMONIC_MEAN
            )
            if rescaleStartDerivatives:
                scaleFactor = vector.magnitude(sd2[0]) / vector.magnitude(
                    md2[0])
                scaleFactorMapStart[n3].append(scaleFactor)
            if rescaleEndDerivatives:
                scaleFactor = vector.magnitude(sd2[-1]) / vector.magnitude(
                    md2[-1])
                scaleFactorMapEnd[n3].append(scaleFactor)

            for n2 in range(1, elementsCountRadial):
                pd2[n3][n2][n1] = sd2[n2]

    ##############
    # Create nodes
    ##############

    nodetemplate = nodes.createNodetemplate()
    nodetemplate.defineField(coordinates)
    nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                          Node.VALUE_LABEL_VALUE, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                          Node.VALUE_LABEL_D_DS1, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                          Node.VALUE_LABEL_D_DS2, 1)
    if useCrossDerivatives:
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D2_DS1DS2, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                          Node.VALUE_LABEL_D_DS3, 1)
    if useCrossDerivatives:
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D2_DS1DS3, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D2_DS2DS3, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D3_DS1DS2DS3, 1)
    nodetemplateLinearS3 = nodes.createNodetemplate()
    nodetemplateLinearS3.defineField(coordinates)
    nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_VALUE, 1)
    nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1)
    nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1)
    if useCrossDerivatives:
        nodetemplateLinearS3.setValueNumberOfVersions(
            coordinates, -1, Node.VALUE_LABEL_D2_DS1DS2, 1)

    nodeIdentifier = nextNodeIdentifier
    nodeId = [[] for n3 in range(nodesCountWall)]
    for n2 in range(elementsCountRadial + 1):
        for n3 in range(nodesCountWall):
            if (n2 == 0) and (startNodeId is not None):
                rowNodeId = copy.deepcopy(startNodeId[n3])
            elif (n2 == elementsCountRadial) and (endNodeId is not None):
                rowNodeId = copy.deepcopy(endNodeId[n3])
            else:
                rowNodeId = []
                nodetemplate1 = nodetemplate if pd3[n3][
                    n2] else nodetemplateLinearS3
                for n1 in range(nodesCountAround):
                    node = nodes.createNode(nodeIdentifier, nodetemplate1)
                    rowNodeId.append(nodeIdentifier)
                    cache.setNode(node)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_VALUE, 1,
                                                  px[n3][n2][n1])
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1,
                                                  pd1[n3][n2][n1])
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1,
                                                  pd2[n3][n2][n1])
                    if pd3[n3][n2]:
                        coordinates.setNodeParameters(cache, -1,
                                                      Node.VALUE_LABEL_D_DS3,
                                                      1, pd3[n3][n2][n1])
                    nodeIdentifier = nodeIdentifier + 1
            nodeId[n3].append(rowNodeId)

    #################
    # Create elements
    #################

    tricubichermite = eftfactory_tricubichermite(mesh, useCrossDerivatives)
    bicubichermitelinear = eftfactory_bicubichermitelinear(
        mesh, useCrossDerivatives)

    elementIdentifier = nextElementIdentifier

    elementtemplateStandard = mesh.createElementtemplate()
    elementtemplateStandard.setElementShapeType(Element.SHAPE_TYPE_CUBE)
    elementtemplateX = mesh.createElementtemplate()
    elementtemplateX.setElementShapeType(Element.SHAPE_TYPE_CUBE)
    elementsCountWall = nodesCountWall - 1

    for e2 in range(elementsCountRadial):
        nonlinearXi3 = (not rowLinearXi3[e2]) or (not rowLinearXi3[e2 + 1])
        eftFactory = tricubichermite if nonlinearXi3 else bicubichermitelinear
        eftStandard = eftFactory.createEftBasic()
        elementtemplateStandard.defineField(coordinates, -1, eftStandard)
        mapStartDerivatives = (e2 == 0) and (startDerivativesMap
                                             or rescaleStartDerivatives)
        mapStartLinearDerivativeXi3 = nonlinearXi3 and rowLinearXi3[e2]
        mapEndDerivatives = (e2 == (elementsCountRadial - 1)) and (
            endDerivativesMap or rescaleEndDerivatives)
        mapEndLinearDerivativeXi3 = nonlinearXi3 and rowLinearXi3[e2 + 1]
        mapDerivatives = mapStartDerivatives or mapStartLinearDerivativeXi3 or \
                         mapEndDerivatives or mapEndLinearDerivativeXi3
        for e3 in range(elementsCountWall):
            for e1 in range(elementsCountAround):
                en = (e1 + 1) % elementsCountAround
                nids = [
                    nodeId[e3][e2][e1], nodeId[e3][e2][en],
                    nodeId[e3][e2 + 1][e1], nodeId[e3][e2 + 1][en],
                    nodeId[e3 + 1][e2][e1], nodeId[e3 + 1][e2][en],
                    nodeId[e3 + 1][e2 + 1][e1], nodeId[e3 + 1][e2 + 1][en]
                ]
                scaleFactors = []

                if mapDerivatives:
                    eft1 = eftFactory.createEftNoCrossDerivatives()
                    # work out if scaling by global -1
                    scaleMinus1 = mapStartLinearDerivativeXi3 or mapEndLinearDerivativeXi3
                    if (not scaleMinus1
                        ) and mapStartDerivatives and startDerivativesMap:
                        for n3 in range(2):
                            n3Idx = n3 + e3
                            # need to handle 3 or 4 maps (e1 uses last 3, en uses first 3)
                            for map in startDerivativesMap[n3Idx][e1][-3:]:
                                if map and (-1 in map):
                                    scaleMinus1 = True
                                    break
                            for map in startDerivativesMap[n3Idx][en][:3]:
                                if map and (-1 in map):
                                    scaleMinus1 = True
                                    break
                    if (not scaleMinus1
                        ) and mapEndDerivatives and endDerivativesMap:
                        for n3 in range(2):
                            n3Idx = n3 + e3
                            # need to handle 3 or 4 maps (e1 uses last 3, en uses first 3)
                            for map in endDerivativesMap[n3Idx][e1][-3:]:
                                if map and (-1 in map):
                                    scaleMinus1 = True
                                    break
                            for map in endDerivativesMap[n3Idx][en][:3]:
                                if map and (-1 in map):
                                    scaleMinus1 = True
                                    break
                    # make node scale factors vary fastest by local node varying across lower xi
                    nodeScaleFactorIds = []
                    for n3 in range(2):
                        n3Idx = n3 + e3
                        if mapStartDerivatives and rescaleStartDerivatives:
                            for i in range(2):
                                derivativesMap = (
                                    startDerivativesMap[n3Idx][e1][1] if
                                    (i == 0) else startDerivativesMap[n3Idx]
                                    [en][1]) if startDerivativesMap else None
                                nodeScaleFactorIds.append(
                                    getQuadrantID(derivativesMap
                                                  if derivativesMap else (0, 1,
                                                                          0)))
                        if mapEndDerivatives and rescaleEndDerivatives:
                            for i in range(2):
                                derivativesMap = (
                                    endDerivativesMap[n3Idx][e1][1] if
                                    (i == 0) else endDerivativesMap[n3Idx][en]
                                    [1]) if endDerivativesMap else None
                                nodeScaleFactorIds.append(
                                    getQuadrantID(derivativesMap
                                                  if derivativesMap else (0, 1,
                                                                          0)))
                    setEftScaleFactorIds(eft1, [1] if scaleMinus1 else [],
                                         nodeScaleFactorIds)
                    firstNodeScaleFactorIndex = 2 if scaleMinus1 else 1
                    firstStartNodeScaleFactorIndex = \
                        firstNodeScaleFactorIndex if (mapStartDerivatives and rescaleStartDerivatives) else None
                    firstEndNodeScaleFactorIndex = \
                        (firstNodeScaleFactorIndex + (2 if firstStartNodeScaleFactorIndex else 0)) \
                            if (mapEndDerivatives and rescaleEndDerivatives) else None
                    layerNodeScaleFactorIndexOffset = \
                        4 if (firstStartNodeScaleFactorIndex and firstEndNodeScaleFactorIndex) else 2
                    if scaleMinus1:
                        scaleFactors.append(-1.0)
                    for n3 in range(2):
                        n3Idx = n3 + e3
                        if firstStartNodeScaleFactorIndex:
                            scaleFactors.append(scaleFactorMapStart[n3Idx][e1])
                            scaleFactors.append(scaleFactorMapStart[n3Idx][en])
                        if firstEndNodeScaleFactorIndex:
                            scaleFactors.append(scaleFactorMapEnd[n3Idx][e1])
                            scaleFactors.append(scaleFactorMapEnd[n3Idx][en])

                    if mapStartLinearDerivativeXi3:
                        eftFactory.setEftLinearDerivative2(
                            eft1, [1, 5, 2, 6], Node.VALUE_LABEL_D_DS3,
                            [Node.VALUE_LABEL_D2_DS1DS3])
                    if mapStartDerivatives:
                        for i in range(2):
                            lns = [1, 5] if (i == 0) else [2, 6]
                            for n3 in range(2):
                                n3Idx = n3 + e3
                                derivativesMap = \
                                    (startDerivativesMap[n3Idx][e1] if (i == 0) else startDerivativesMap[n3Idx][en]) \
                                        if startDerivativesMap else (None, None, None)
                                # handle different d1 on each side of node
                                d1Map = \
                                    derivativesMap[0] if ((i == 1) or (len(derivativesMap) < 4)) else derivativesMap[3]
                                d2Map = derivativesMap[1] if derivativesMap[
                                    1] else (0, 1, 0)
                                d3Map = derivativesMap[2]
                                # use temporary to safely swap DS1 and DS2:
                                ln = [lns[n3]]
                                if d1Map:
                                    remapEftNodeValueLabel(
                                        eft1, ln, Node.VALUE_LABEL_D_DS1,
                                        [(Node.VALUE_LABEL_D2_DS1DS2, [])])
                                if d3Map:
                                    remapEftNodeValueLabel(
                                        eft1, ln, Node.VALUE_LABEL_D_DS3,
                                        [(Node.VALUE_LABEL_D2_DS2DS3, [])])
                                if d2Map:
                                    remapEftNodeValueLabel(
                                        eft1, ln, Node.VALUE_LABEL_D_DS2,
                                        derivativeSignsToExpressionTerms(
                                            (Node.VALUE_LABEL_D_DS1,
                                             Node.VALUE_LABEL_D_DS2,
                                             Node.VALUE_LABEL_D_DS3), d2Map,
                                            (firstStartNodeScaleFactorIndex +
                                             i + n3 *
                                             layerNodeScaleFactorIndexOffset)
                                            if rescaleStartDerivatives else
                                            None))
                                if d1Map:
                                    remapEftNodeValueLabel(
                                        eft1, ln, Node.VALUE_LABEL_D2_DS1DS2,
                                        derivativeSignsToExpressionTerms(
                                            (Node.VALUE_LABEL_D_DS1,
                                             Node.VALUE_LABEL_D_DS2,
                                             Node.VALUE_LABEL_D_DS3), d1Map))
                                if d3Map:
                                    remapEftNodeValueLabel(
                                        eft1, ln, Node.VALUE_LABEL_D2_DS2DS3,
                                        derivativeSignsToExpressionTerms(
                                            (Node.VALUE_LABEL_D_DS1,
                                             Node.VALUE_LABEL_D_DS2,
                                             Node.VALUE_LABEL_D_DS3), d3Map))
                    if mapEndLinearDerivativeXi3:
                        eftFactory.setEftLinearDerivative2(
                            eft1, [3, 7, 4, 8], Node.VALUE_LABEL_D_DS3,
                            [Node.VALUE_LABEL_D2_DS1DS3])
                    if mapEndDerivatives:
                        for i in range(2):
                            lns = [3, 7] if (i == 0) else [4, 8]
                            for n3 in range(2):
                                n3Idx = n3 + e3
                                derivativesMap = \
                                    (endDerivativesMap[n3Idx][e1] if (i == 0) else endDerivativesMap[n3Idx][en]) \
                                        if endDerivativesMap else (None, None, None)
                                # handle different d1 on each side of node
                                d1Map = derivativesMap[0] if ((i == 1) or (len(derivativesMap) < 4)) else \
                                    derivativesMap[3]
                                d2Map = derivativesMap[1] if derivativesMap[
                                    1] else (0, 1, 0)
                                d3Map = derivativesMap[2]

                                # use temporary to safely swap DS1 and DS2:
                                ln = [lns[n3]]
                                if d1Map:
                                    remapEftNodeValueLabel(
                                        eft1, ln, Node.VALUE_LABEL_D_DS1,
                                        [(Node.VALUE_LABEL_D2_DS1DS2, [])])
                                if d3Map:
                                    remapEftNodeValueLabel(
                                        eft1, ln, Node.VALUE_LABEL_D_DS3,
                                        [(Node.VALUE_LABEL_D2_DS2DS3, [])])
                                if d2Map:
                                    remapEftNodeValueLabel(
                                        eft1, ln, Node.VALUE_LABEL_D_DS2,
                                        derivativeSignsToExpressionTerms(
                                            (Node.VALUE_LABEL_D_DS1,
                                             Node.VALUE_LABEL_D_DS2,
                                             Node.VALUE_LABEL_D_DS3), d2Map,
                                            (firstEndNodeScaleFactorIndex + i +
                                             n3 *
                                             layerNodeScaleFactorIndexOffset)
                                            if rescaleEndDerivatives else
                                            None))
                                if d1Map:
                                    remapEftNodeValueLabel(
                                        eft1, ln, Node.VALUE_LABEL_D2_DS1DS2,
                                        derivativeSignsToExpressionTerms(
                                            (Node.VALUE_LABEL_D_DS1,
                                             Node.VALUE_LABEL_D_DS2,
                                             Node.VALUE_LABEL_D_DS3), d1Map))
                                if d3Map:
                                    remapEftNodeValueLabel(
                                        eft1, ln, Node.VALUE_LABEL_D2_DS2DS3,
                                        derivativeSignsToExpressionTerms(
                                            (Node.VALUE_LABEL_D_DS1,
                                             Node.VALUE_LABEL_D_DS2,
                                             Node.VALUE_LABEL_D_DS3), d3Map))

                    elementtemplateX.defineField(coordinates, -1, eft1)
                    elementtemplate1 = elementtemplateX
                else:
                    eft1 = eftStandard
                    elementtemplate1 = elementtemplateStandard

                element = mesh.createElement(elementIdentifier,
                                             elementtemplate1)
                result2 = element.setNodesByIdentifier(eft1, nids)
                if scaleFactors:
                    result3 = element.setScaleFactors(eft1, scaleFactors)
                # print('create element annulus', element.isValid(), elementIdentifier, eft1.validate(),
                #       result2, result3 if scaleFactors else None, nids)
                elementIdentifier += 1

                if rowMeshGroups:
                    for meshGroup in rowMeshGroups[e2]:
                        meshGroup.addElement(element)

                if wallAnnotationGroups:
                    for annotationGroup in wallAnnotationGroups[e3]:
                        meshGroup = annotationGroup.getMeshGroup(mesh)
                        meshGroup.addElement(element)

    fm.endChange()

    return nodeIdentifier, elementIdentifier
Beispiel #4
0
    def generateBaseMesh(region, options):
        """
        Generate the base tricubic Hermite mesh. See also generateMesh().
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: None
        """
        elementsCountAround = options['Number of elements around']
        elementsCountAlong = options['Number of elements along']
        elementsCountThroughWall = options['Number of elements through wall']
        wallThickness = options['Wall thickness']
        useCrossDerivatives = options['Use cross derivatives']

        fm = region.getFieldmodule()
        fm.beginChange()
        coordinates = findOrCreateFieldCoordinates(fm)

        nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
        nodetemplate = nodes.createNodetemplate()
        nodetemplate.defineField(coordinates)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_VALUE, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS1, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS2, 1)
        if useCrossDerivatives:
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS1DS2,
                                                  1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS3, 1)
        if useCrossDerivatives:
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS1DS3,
                                                  1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS2DS3,
                                                  1)
            nodetemplate.setValueNumberOfVersions(
                coordinates, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1)

        mesh = fm.findMeshByDimension(3)

        tricubichermite = eftfactory_tricubichermite(mesh, useCrossDerivatives)
        eft = tricubichermite.createEftBasic()

        elementtemplate = mesh.createElementtemplate()
        elementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        result = elementtemplate.defineField(coordinates, -1, eft)

        cache = fm.createFieldcache()

        # create nodes
        nodeIdentifier = 1
        radiansPerElementAround = 2.0 * math.pi / elementsCountAround
        wallThicknessPerElement = wallThickness / elementsCountThroughWall
        x = [0.0, 0.0, 0.0]
        dx_ds1 = [0.0, 0.0, 0.0]
        dx_ds2 = [0.0, 0.0, 1.0 / elementsCountAlong]
        dx_ds3 = [0.0, 0.0, 0.0]
        zero = [0.0, 0.0, 0.0]
        for n3 in range(elementsCountThroughWall + 1):
            radius = 0.5 + wallThickness * (n3 / elementsCountThroughWall -
                                            1.0)
            for n2 in range(elementsCountAlong + 1):
                x[2] = n2 / elementsCountAlong
                for n1 in range(elementsCountAround):
                    radiansAround = n1 * radiansPerElementAround
                    cosRadiansAround = math.cos(radiansAround)
                    sinRadiansAround = math.sin(radiansAround)
                    x[0] = radius * cosRadiansAround
                    x[1] = radius * sinRadiansAround
                    dx_ds1[
                        0] = radiansPerElementAround * radius * -sinRadiansAround
                    dx_ds1[
                        1] = radiansPerElementAround * radius * cosRadiansAround
                    dx_ds3[0] = wallThicknessPerElement * cosRadiansAround
                    dx_ds3[1] = wallThicknessPerElement * sinRadiansAround
                    node = nodes.createNode(nodeIdentifier, nodetemplate)
                    cache.setNode(node)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_VALUE, 1, x)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1,
                                                  dx_ds1)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1,
                                                  dx_ds2)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS3, 1,
                                                  dx_ds3)
                    if useCrossDerivatives:
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D2_DS1DS2, 1, zero)
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D2_DS1DS3, 1, zero)
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D2_DS2DS3, 1, zero)
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1, zero)
                    nodeIdentifier = nodeIdentifier + 1

        # create elements
        elementIdentifier = 1
        now = (elementsCountAlong + 1) * elementsCountAround
        for e3 in range(elementsCountThroughWall):
            for e2 in range(elementsCountAlong):
                for e1 in range(elementsCountAround):
                    element = mesh.createElement(elementIdentifier,
                                                 elementtemplate)
                    bni11 = e3 * now + e2 * elementsCountAround + e1 + 1
                    bni12 = e3 * now + e2 * elementsCountAround + (
                        e1 + 1) % elementsCountAround + 1
                    bni21 = e3 * now + (e2 + 1) * elementsCountAround + e1 + 1
                    bni22 = e3 * now + (e2 + 1) * elementsCountAround + (
                        e1 + 1) % elementsCountAround + 1
                    nodeIdentifiers = [
                        bni11, bni12, bni21, bni22, bni11 + now, bni12 + now,
                        bni21 + now, bni22 + now
                    ]
                    result = element.setNodesByIdentifier(eft, nodeIdentifiers)
                    elementIdentifier = elementIdentifier + 1

        fm.endChange()
Beispiel #5
0
def createNodesAndElements(region, x, d1, d2, d3, xFlat, d1Flat, d2Flat,
                           xOrgan, d1Organ, d2Organ, organCoordinateFieldName,
                           elementsCountAround, elementsCountAlong,
                           elementsCountThroughWall, annotationGroupsAround,
                           annotationGroupsAlong, annotationGroupsThroughWall,
                           firstNodeIdentifier, firstElementIdentifier,
                           useCubicHermiteThroughWall, useCrossDerivatives,
                           closedProximalEnd):
    """
    Create nodes and elements for the coordinates and flat coordinates fields.
    :param x, d1, d2, d3: coordinates and derivatives of coordinates field.
    :param xFlat, d1Flat, d2Flat, d3Flat: coordinates and derivatives of
    flat coordinates field.
    :param xOrgan, d1Organ, d2Organ, d3Organ, organCoordinateFieldName: coordinates, derivatives and name of organ
    coordinates field.
    :param elementsCountAround: Number of elements around tube.
    :param elementsCountAlong: Number of elements along tube.
    :param elementsCountThroughWall: Number of elements through wall.
    :param annotationGroupsAround: Annotation groups of elements around.
    :param annotationGroupsAlong: Annotation groups of elements along.
    :param annotationGroupsThroughWall: Annotation groups of elements through wall.
    :param firstNodeIdentifier, firstElementIdentifier: first node and
    element identifier to use.
    :param useCubicHermiteThroughWall: use linear when false
    :param useCrossDerivatives: use cross derivatives when true
    :return nodeIdentifier, elementIdentifier, allAnnotationGroups
    """

    nodeIdentifier = firstNodeIdentifier
    elementIdentifier = firstElementIdentifier
    zero = [0.0, 0.0, 0.0]

    fm = region.getFieldmodule()
    fm.beginChange()
    cache = fm.createFieldcache()

    # Coordinates field
    coordinates = findOrCreateFieldCoordinates(fm)
    nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
    nodetemplate = nodes.createNodetemplate()
    nodetemplate.defineField(coordinates)
    nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                          Node.VALUE_LABEL_VALUE, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                          Node.VALUE_LABEL_D_DS1, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                          Node.VALUE_LABEL_D_DS2, 1)
    if useCrossDerivatives:
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D2_DS1DS2, 1)
    if useCubicHermiteThroughWall:
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS3, 1)
        if useCrossDerivatives:
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS1DS3,
                                                  1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS2DS3,
                                                  1)
            nodetemplate.setValueNumberOfVersions(
                coordinates, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1)

    mesh = fm.findMeshByDimension(3)

    if useCubicHermiteThroughWall:
        eftfactory = eftfactory_tricubichermite(mesh, useCrossDerivatives)
    else:
        eftfactory = eftfactory_bicubichermitelinear(mesh, useCrossDerivatives)
    eft = eftfactory.createEftBasic()

    elementtemplate = mesh.createElementtemplate()
    elementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
    result = elementtemplate.defineField(coordinates, -1, eft)

    if xFlat:
        # Flat coordinates field
        bicubichermitelinear = eftfactory_bicubichermitelinear(
            mesh, useCrossDerivatives)
        eftFlat1 = bicubichermitelinear.createEftBasic()
        eftFlat2 = bicubichermitelinear.createEftOpenTube()

        flatCoordinates = findOrCreateFieldCoordinates(fm,
                                                       name="flat coordinates")
        flatNodetemplate1 = nodes.createNodetemplate()
        flatNodetemplate1.defineField(flatCoordinates)
        flatNodetemplate1.setValueNumberOfVersions(flatCoordinates, -1,
                                                   Node.VALUE_LABEL_VALUE, 1)
        flatNodetemplate1.setValueNumberOfVersions(flatCoordinates, -1,
                                                   Node.VALUE_LABEL_D_DS1, 1)
        flatNodetemplate1.setValueNumberOfVersions(flatCoordinates, -1,
                                                   Node.VALUE_LABEL_D_DS2, 1)
        if useCrossDerivatives:
            flatNodetemplate1.setValueNumberOfVersions(
                flatCoordinates, -1, Node.VALUE_LABEL_D2_DS1DS2, 1)

        flatNodetemplate2 = nodes.createNodetemplate()
        flatNodetemplate2.defineField(flatCoordinates)
        flatNodetemplate2.setValueNumberOfVersions(flatCoordinates, -1,
                                                   Node.VALUE_LABEL_VALUE, 2)
        flatNodetemplate2.setValueNumberOfVersions(flatCoordinates, -1,
                                                   Node.VALUE_LABEL_D_DS1, 2)
        flatNodetemplate2.setValueNumberOfVersions(flatCoordinates, -1,
                                                   Node.VALUE_LABEL_D_DS2, 2)
        if useCrossDerivatives:
            flatNodetemplate2.setValueNumberOfVersions(
                flatCoordinates, -1, Node.VALUE_LABEL_D2_DS1DS2, 2)

        flatElementtemplate1 = mesh.createElementtemplate()
        flatElementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        flatElementtemplate1.defineField(flatCoordinates, -1, eftFlat1)

        flatElementtemplate2 = mesh.createElementtemplate()
        flatElementtemplate2.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        flatElementtemplate2.defineField(flatCoordinates, -1, eftFlat2)

    if xOrgan:
        # Organ coordinates field
        bicubichermitelinear = eftfactory_bicubichermitelinear(
            mesh, useCrossDerivatives)
        eftOrgan = bicubichermitelinear.createEftBasic()

        organCoordinates = findOrCreateFieldCoordinates(
            fm, name=organCoordinateFieldName)
        organNodetemplate = nodes.createNodetemplate()
        organNodetemplate.defineField(organCoordinates)
        organNodetemplate.setValueNumberOfVersions(organCoordinates, -1,
                                                   Node.VALUE_LABEL_VALUE, 1)
        organNodetemplate.setValueNumberOfVersions(organCoordinates, -1,
                                                   Node.VALUE_LABEL_D_DS1, 1)
        organNodetemplate.setValueNumberOfVersions(organCoordinates, -1,
                                                   Node.VALUE_LABEL_D_DS2, 1)
        if useCrossDerivatives:
            organNodetemplate.setValueNumberOfVersions(
                organCoordinates, -1, Node.VALUE_LABEL_D2_DS1DS2, 1)

        organElementtemplate = mesh.createElementtemplate()
        organElementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        organElementtemplate.defineField(organCoordinates, -1, eftOrgan)

    # Create nodes
    # Coordinates field
    for n in range(len(x)):
        node = nodes.createNode(nodeIdentifier, nodetemplate)
        cache.setNode(node)
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1,
                                      x[n])
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1,
                                      d1[n])
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1,
                                      d2[n])
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1,
                                      d3[n])
        if useCrossDerivatives:
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D2_DS1DS2, 1, zero)
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D2_DS1DS3, 1, zero)
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D2_DS2DS3, 1, zero)
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D3_DS1DS2DS3, 1,
                                          zero)
        # print('NodeIdentifier = ', nodeIdentifier, x[n], d1[n], d2[n])
        nodeIdentifier = nodeIdentifier + 1

    # Flat coordinates field
    if xFlat:
        nodeIdentifier = firstNodeIdentifier
        for n2 in range(elementsCountAlong + 1):
            for n3 in range(elementsCountThroughWall + 1):
                for n1 in range(elementsCountAround):
                    i = n2 * (elementsCountAround +
                              1) * (elementsCountThroughWall +
                                    1) + (elementsCountAround + 1) * n3 + n1
                    node = nodes.findNodeByIdentifier(nodeIdentifier)
                    node.merge(flatNodetemplate2 if n1 ==
                               0 else flatNodetemplate1)
                    cache.setNode(node)
                    # print('NodeIdentifier', nodeIdentifier, 'version 1, xList Index =', i+1)
                    flatCoordinates.setNodeParameters(cache, -1,
                                                      Node.VALUE_LABEL_VALUE,
                                                      1, xFlat[i])
                    flatCoordinates.setNodeParameters(cache, -1,
                                                      Node.VALUE_LABEL_D_DS1,
                                                      1, d1Flat[i])
                    flatCoordinates.setNodeParameters(cache, -1,
                                                      Node.VALUE_LABEL_D_DS2,
                                                      1, d2Flat[i])
                    if useCrossDerivatives:
                        flatCoordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D2_DS1DS2, 1, zero)
                    if n1 == 0:
                        # print('NodeIdentifier', nodeIdentifier, 'version 2, xList Index =', i+elementsCountAround+1)
                        flatCoordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_VALUE, 2,
                            xFlat[i + elementsCountAround])
                        flatCoordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D_DS1, 2,
                            d1Flat[i + elementsCountAround])
                        flatCoordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D_DS2, 2,
                            d2Flat[i + elementsCountAround])
                        if useCrossDerivatives:
                            flatCoordinates.setNodeParameters(
                                cache, -1, Node.VALUE_LABEL_D2_DS1DS2, 2, zero)
                    nodeIdentifier = nodeIdentifier + 1

    # Organ coordinates field
    if xOrgan:
        nodeIdentifier = firstNodeIdentifier
        for n in range(len(xOrgan)):
            node = nodes.findNodeByIdentifier(nodeIdentifier)
            node.merge(organNodetemplate)
            cache.setNode(node)
            organCoordinates.setNodeParameters(cache, -1,
                                               Node.VALUE_LABEL_VALUE, 1,
                                               xOrgan[n])
            organCoordinates.setNodeParameters(cache, -1,
                                               Node.VALUE_LABEL_D_DS1, 1,
                                               d1Organ[n])
            organCoordinates.setNodeParameters(cache, -1,
                                               Node.VALUE_LABEL_D_DS2, 1,
                                               d2Organ[n])
            if useCrossDerivatives:
                organCoordinates.setNodeParameters(cache, -1,
                                                   Node.VALUE_LABEL_D2_DS1DS2,
                                                   1, zero)
            nodeIdentifier = nodeIdentifier + 1

    # create elements
    elementtemplate3 = mesh.createElementtemplate()
    elementtemplate3.setElementShapeType(Element.SHAPE_TYPE_CUBE)
    radiansPerElementAround = math.pi * 2.0 / elementsCountAround

    allAnnotationGroups = []

    if closedProximalEnd:
        # Create apex
        for e3 in range(elementsCountThroughWall):
            for e1 in range(elementsCountAround):
                va = e1
                vb = (e1 + 1) % elementsCountAround
                eft1 = eftfactory.createEftShellPoleBottom(va * 100, vb * 100)
                elementtemplate3.defineField(coordinates, -1, eft1)
                element = mesh.createElement(elementIdentifier,
                                             elementtemplate3)
                bni1 = e3 + 1
                bni2 = elementsCountThroughWall + 1 + elementsCountAround * e3 + e1 + 1
                bni3 = elementsCountThroughWall + 1 + elementsCountAround * e3 + (
                    e1 + 1) % elementsCountAround + 1
                nodeIdentifiers = [
                    bni1, bni2, bni3, bni1 + 1, bni2 + elementsCountAround,
                    bni3 + elementsCountAround
                ]
                element.setNodesByIdentifier(eft1, nodeIdentifiers)
                # set general linear map coefficients
                radiansAround = e1 * radiansPerElementAround
                radiansAroundNext = (
                    (e1 + 1) % elementsCountAround) * radiansPerElementAround
                scalefactors = [
                    -1.0,
                    math.sin(radiansAround),
                    math.cos(radiansAround), radiansPerElementAround,
                    math.sin(radiansAroundNext),
                    math.cos(radiansAroundNext), radiansPerElementAround,
                    math.sin(radiansAround),
                    math.cos(radiansAround), radiansPerElementAround,
                    math.sin(radiansAroundNext),
                    math.cos(radiansAroundNext), radiansPerElementAround
                ]
                result = element.setScaleFactors(eft1, scalefactors)
                elementIdentifier = elementIdentifier + 1
                annotationGroups = annotationGroupsAround[e1] + annotationGroupsAlong[0] + \
                                   annotationGroupsThroughWall[e3]
                if annotationGroups:
                    allAnnotationGroups = mergeAnnotationGroups(
                        allAnnotationGroups, annotationGroups)
                    for annotationGroup in annotationGroups:
                        meshGroup = annotationGroup.getMeshGroup(mesh)
                        meshGroup.addElement(element)

    # Create regular elements
    now = elementsCountAround * (elementsCountThroughWall + 1)
    for e2 in range(1 if closedProximalEnd else 0, elementsCountAlong):
        for e3 in range(elementsCountThroughWall):
            for e1 in range(elementsCountAround):
                if closedProximalEnd:
                    bni11 = (e2 -
                             1) * now + e3 * elementsCountAround + e1 + 1 + (
                                 elementsCountThroughWall + 1)
                    bni12 = (e2-1) * now + e3 * elementsCountAround + (e1 + 1) % elementsCountAround + 1 + \
                            (elementsCountThroughWall + 1)
                    bni21 = (e2 - 1) * now + (
                        e3 + 1) * elementsCountAround + e1 + 1 + (
                            elementsCountThroughWall + 1)
                    bni22 = (e2-1) * now + (e3 + 1) * elementsCountAround + (e1 + 1) % elementsCountAround + 1 + \
                            (elementsCountThroughWall + 1)
                else:
                    bni11 = e2 * now + e3 * elementsCountAround + e1 + 1
                    bni12 = e2 * now + e3 * elementsCountAround + (
                        e1 + 1) % elementsCountAround + 1
                    bni21 = e2 * now + (e3 + 1) * elementsCountAround + e1 + 1
                    bni22 = e2 * now + (e3 + 1) * elementsCountAround + (
                        e1 + 1) % elementsCountAround + 1
                nodeIdentifiers = [
                    bni11, bni12, bni11 + now, bni12 + now, bni21, bni22,
                    bni21 + now, bni22 + now
                ]
                onOpening = e1 > elementsCountAround - 2
                element = mesh.createElement(elementIdentifier,
                                             elementtemplate)
                element.setNodesByIdentifier(eft, nodeIdentifiers)
                if xFlat:
                    element.merge(flatElementtemplate2
                                  if onOpening else flatElementtemplate1)
                    element.setNodesByIdentifier(
                        eftFlat2 if onOpening else eftFlat1, nodeIdentifiers)
                if xOrgan:
                    element.merge(organElementtemplate)
                    element.setNodesByIdentifier(eftOrgan, nodeIdentifiers)
                elementIdentifier = elementIdentifier + 1

                annotationGroups = annotationGroupsAround[e1] + annotationGroupsAlong[e2] + \
                                   annotationGroupsThroughWall[e3]
                if annotationGroups:
                    allAnnotationGroups = mergeAnnotationGroups(
                        allAnnotationGroups, annotationGroups)
                    for annotationGroup in annotationGroups:
                        meshGroup = annotationGroup.getMeshGroup(mesh)
                        meshGroup.addElement(element)

    fm.endChange()

    return nodeIdentifier, elementIdentifier, allAnnotationGroups
    def generateMesh(region, options):
        """
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: None
        """
        elementsCount1 = options['Number of elements 1']
        elementsCount2 = options['Number of elements 2']
        elementsCount3 = options['Number of elements 3']
        elementsCountThroughWall = options['Number of elements through wall']
        elementsCountAround = 2 * (elementsCount1 + elementsCount2)
        holeRadius = options['Hole diameter'] * 0.5
        useCrossDerivatives = options['Use cross derivatives']

        fm = region.getFieldmodule()
        fm.beginChange()
        coordinates = zinc_utils.getOrCreateCoordinateField(fm)

        nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
        nodetemplate = nodes.createNodetemplate()
        nodetemplate.defineField(coordinates)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_VALUE, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS1, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS2, 1)
        if useCrossDerivatives:
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS1DS2,
                                                  1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS3, 1)
        if useCrossDerivatives:
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS1DS3,
                                                  1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS2DS3,
                                                  1)
            nodetemplate.setValueNumberOfVersions(
                coordinates, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1)

        mesh = fm.findMeshByDimension(3)

        tricubichermite = eftfactory_tricubichermite(mesh, useCrossDerivatives)
        eft = tricubichermite.createEftBasic()

        tricubicHermiteBasis = fm.createElementbasis(
            3, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE)

        # note I'm cheating here by using element-based scale factors
        # i.e. not shared by neighbouring elements, and I'm also using the same scale
        # factors for the bottom and top of each element
        eftOuter1 = mesh.createElementfieldtemplate(tricubicHermiteBasis)
        eftOuter2 = mesh.createElementfieldtemplate(tricubicHermiteBasis)
        i = 0
        for eftOuter in [eftOuter1, eftOuter2]:
            i += 1
            eftOuter.setNumberOfLocalScaleFactors(10)
            eft.setScaleFactorType(
                1, Elementfieldtemplate.SCALE_FACTOR_TYPE_GLOBAL_GENERAL)
            # GRC: allow scale factor identifier for global -1 to be prescribed
            eft.setScaleFactorIdentifier(1, 1)
            # Global scale factor 4 to subtract lateral derivative term from cross derivative to fix edges
            eft.setScaleFactorType(
                2, Elementfieldtemplate.SCALE_FACTOR_TYPE_GLOBAL_GENERAL)
            eft.setScaleFactorIdentifier(2, 2)
            nonCrossDerivativesNodes = [0, 1, 4, 5
                                        ] if useCrossDerivatives else range(8)
            for n in nonCrossDerivativesNodes:
                eftOuter.setFunctionNumberOfTerms(n * 8 + 4, 0)
                eftOuter.setFunctionNumberOfTerms(n * 8 + 6, 0)
                eftOuter.setFunctionNumberOfTerms(n * 8 + 7, 0)
                eftOuter.setFunctionNumberOfTerms(n * 8 + 8, 0)
            # nodes 1,2,5,6: general map dxi1, dsxi2 from ds1, ds2
            for n in [0, 1, 4, 5]:
                ln = n + 1
                sfo = (n % 2) * 4 + 2
                eftOuter.setFunctionNumberOfTerms(n * 8 + 2, 2)
                eftOuter.setTermNodeParameter(n * 8 + 2, 1, ln,
                                              Node.VALUE_LABEL_D_DS1, 1)
                eftOuter.setTermScaling(n * 8 + 2, 1, [1 + sfo])
                eftOuter.setTermNodeParameter(n * 8 + 2, 2, ln,
                                              Node.VALUE_LABEL_D_DS2, 1)
                eftOuter.setTermScaling(n * 8 + 2, 2, [2 + sfo])
                eftOuter.setFunctionNumberOfTerms(n * 8 + 3, 2)
                eftOuter.setTermNodeParameter(n * 8 + 3, 1, ln,
                                              Node.VALUE_LABEL_D_DS1, 1)
                eftOuter.setTermScaling(n * 8 + 3, 1, [3 + sfo])
                eftOuter.setTermNodeParameter(n * 8 + 3, 2, ln,
                                              Node.VALUE_LABEL_D_DS2, 1)
                eftOuter.setTermScaling(n * 8 + 3, 2, [4 + sfo])
                # map d2_dxi1dxi2 to subtract corner angle terms to fix edge continuity
                eftOuter.setFunctionNumberOfTerms(n * 8 + 4, 1)
                if i == 1:
                    eftOuter.setTermNodeParameter(n * 8 + 4, 1, ln,
                                                  Node.VALUE_LABEL_D_DS1, 1)
                    if (n % 2) == 0:
                        eftOuter.setTermScaling(n * 8 + 4, 1, [1, 2, 3 + sfo])
                    else:
                        eftOuter.setTermScaling(n * 8 + 4, 1, [2, 3 + sfo])
                else:
                    eftOuter.setTermNodeParameter(n * 8 + 4, 1, ln,
                                                  Node.VALUE_LABEL_D_DS2, 1)
                    if (n % 2) == 0:
                        eftOuter.setTermScaling(n * 8 + 4, 1, [1, 2, 4 + sfo])
                    else:
                        eftOuter.setTermScaling(n * 8 + 4, 1, [2, 4 + sfo])

        elementtemplate = mesh.createElementtemplate()
        elementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        result = elementtemplate.defineField(coordinates, -1, eft)

        elementtemplateOuter1 = mesh.createElementtemplate()
        elementtemplateOuter1.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        result = elementtemplateOuter1.defineField(coordinates, -1, eftOuter1)
        elementtemplateOuter2 = mesh.createElementtemplate()
        elementtemplateOuter2.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        result = elementtemplateOuter2.defineField(coordinates, -1, eftOuter2)

        cache = fm.createFieldcache()

        # create nodes
        radiansPerElementAround = 2.0 * math.pi / elementsCountAround
        wallThicknessMin = (0.5 - holeRadius)
        wallThicknessMax = math.sqrt(0.5) - holeRadius
        wallThicknessPerElement = wallThicknessMin / elementsCountThroughWall
        radius = holeRadius
        inner_x = []
        inner_d1 = []
        inner_d2 = []
        startRadians = math.pi * (-0.5 - elementsCount1 / elementsCountAround)
        for n1 in range(elementsCountAround):
            radiansAround = startRadians + n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            inner_x.append(
                (radius * cosRadiansAround, radius * sinRadiansAround))
            inner_d1.append(
                (radiansPerElementAround * radius * -sinRadiansAround,
                 radiansPerElementAround * radius * cosRadiansAround))
            #inner_d2.append((wallThicknessPerElement*cosRadiansAround, wallThicknessPerElement*sinRadiansAround))
            inner_d2.append((wallThicknessMin * cosRadiansAround,
                             wallThicknessMin * sinRadiansAround))
        outer_x = []
        outer_d1 = []
        outer_d2 = []
        mag = math.sqrt(elementsCount1 * elementsCount1 +
                        elementsCount2 * elementsCount2)
        outer_dx1_ds1 = 1.0 / elementsCount1
        outer_dx2_ds2 = 1.0 / elementsCount2
        #cornerScale1 = 1.0 / max(elementsCount1 + 1, elementsCount2 + 1)
        #cornerScale1 = 1.0 / min(2, max(elementsCount1, elementsCount2))
        cornerScale1 = 1.0 / max(elementsCount1 + elementsCountThroughWall,
                                 elementsCount2 + elementsCountThroughWall)
        sqrt05 = math.sqrt(0.5)
        wallThicknessMax = sqrt05 - holeRadius
        for n in range(elementsCount1):
            x = -0.5 + n / elementsCount1
            outer_x.append((x, -0.5))
            if n == 0:
                rx = x / sqrt05
                ry = -0.5 / sqrt05
                scale2 = wallThicknessMax
                outer_d1.append((-ry * cornerScale1, rx * cornerScale1))
                outer_d2.append((rx * scale2, ry * scale2))
            else:
                scale2 = wallThicknessMin
                outer_d1.append((outer_dx1_ds1, 0.0))
                outer_d2.append((0.0, -scale2))
        for n in range(elementsCount2):
            y = -0.5 + n / elementsCount2
            outer_x.append((0.5, y))
            if n == 0:
                rx = 0.5 / sqrt05
                ry = y / sqrt05
                scale2 = wallThicknessMax
                outer_d1.append((-ry * cornerScale1, rx * cornerScale1))
                outer_d2.append((rx * scale2, ry * scale2))
            else:
                scale2 = wallThicknessMin
                outer_d1.append((0.0, outer_dx2_ds2))
                outer_d2.append((scale2, 0.0))
        for n in range(elementsCount1):
            x = 0.5 - n / elementsCount1
            outer_x.append((x, 0.5))
            if n == 0:
                rx = x / sqrt05
                ry = 0.5 / sqrt05
                scale2 = wallThicknessMax
                outer_d1.append((-ry * cornerScale1, rx * cornerScale1))
                outer_d2.append((rx * scale2, ry * scale2))
            else:
                scale2 = wallThicknessMin
                outer_d1.append((-outer_dx1_ds1, 0.0))
                outer_d2.append((0.0, scale2))
        for n in range(elementsCount2):
            y = 0.5 - n / elementsCount2
            outer_x.append((-0.5, y))
            if n == 0:
                rx = -0.5 / sqrt05
                ry = y / sqrt05
                scale2 = wallThicknessMax
                outer_d1.append((-ry * cornerScale1, rx * cornerScale1))
                outer_d2.append((rx * scale2, ry * scale2))
            else:
                scale2 = wallThicknessMin
                outer_d1.append((0.0, -outer_dx2_ds2))
                outer_d2.append((-scale2, 0.0))

        nodeIdentifier = 1
        x = [0.0, 0.0, 0.0]
        dx_ds1 = [0.0, 0.0, 0.0]
        dx_ds2 = [0.0, 0.0, 0.0]
        dx_ds3 = [0.0, 0.0, 1.0 / elementsCount3]
        outer_dx_ds1 = [1.0 / elementsCount1, 0.0, 0.0]
        outer_dx_ds2 = [0.0, 1.0 / elementsCount2, 0.0]
        outer_dx_ds3 = [0.0, 0.0, 1.0 / elementsCount3]
        zero = [0.0, 0.0, 0.0]
        for n3 in range(elementsCount3 + 1):
            x[2] = n3 / elementsCount3
            # outer nodes
            for n1 in range(elementsCountAround):
                x[0] = outer_x[n1][0]
                x[1] = outer_x[n1][1]
                node = nodes.createNode(nodeIdentifier, nodetemplate)
                cache.setNode(node)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_VALUE, 1, x)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS1, 1,
                                              outer_dx_ds1)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS2, 1,
                                              outer_dx_ds2)
                #coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, [ outer_d1[n1][0], outer_d1[n1][1], 0.0 ])
                #coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, [ outer_d2[n1][0], outer_d2[n1][1], 0.0 ])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS3, 1,
                                              outer_dx_ds3)
                if useCrossDerivatives:
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D2_DS1DS2,
                                                  1, zero)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D2_DS1DS3,
                                                  1, zero)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D2_DS2DS3,
                                                  1, zero)
                    coordinates.setNodeParameters(
                        cache, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1, zero)
                nodeIdentifier = nodeIdentifier + 1
            # inner nodes
            for n2 in range(elementsCountThroughWall):
                xir = (n2 + 1) / elementsCountThroughWall
                xi = 1.0 - xir
                for n1 in range(elementsCountAround):
                    node = nodes.createNode(nodeIdentifier, nodetemplate)
                    cache.setNode(node)
                    v = interpolateCubicHermite(inner_x[n1], inner_d2[n1],
                                                outer_x[n1], outer_d2[n1], xi)
                    x[0] = v[0]
                    x[1] = v[1]
                    dx_ds1[0] = xir * inner_d1[n1][0] + xi * outer_d1[n1][0]
                    dx_ds1[1] = xir * inner_d1[n1][1] + xi * outer_d1[n1][1]
                    d2 = interpolateCubicHermiteDerivative(
                        inner_x[n1], inner_d2[n1], outer_x[n1], outer_d2[n1],
                        xi)
                    dx_ds2[0] = -d2[
                        0] / elementsCountThroughWall  # *wallThicknessPerElement
                    dx_ds2[1] = -d2[
                        1] / elementsCountThroughWall  # *wallThicknessPerElement
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_VALUE, 1, x)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1,
                                                  dx_ds1)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1,
                                                  dx_ds2)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS3, 1,
                                                  dx_ds3)
                    if useCrossDerivatives:
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D2_DS1DS2, 1, zero)
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D2_DS1DS3, 1, zero)
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D2_DS2DS3, 1, zero)
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1, zero)
                    nodeIdentifier = nodeIdentifier + 1

        # create elements
        elementIdentifier = 1
        no3 = (elementsCountThroughWall + 1) * elementsCountAround
        for e3 in range(elementsCount3):
            # first row general maps ds1, ds2 to dxi1, dxi2
            for e1 in range(elementsCountAround):
                en = (e1 + 1) % elementsCountAround
                onX = (e1 % (elementsCount1 + elementsCount2)) < elementsCount1
                elementtemplateOuter = elementtemplateOuter1 if onX else elementtemplateOuter2
                eftOuter = eftOuter1 if onX else eftOuter2
                element = mesh.createElement(elementIdentifier,
                                             elementtemplateOuter)
                bni11 = e3 * no3 + e1 + 1
                bni12 = e3 * no3 + en + 1
                bni21 = e3 * no3 + elementsCountAround + e1 + 1
                bni22 = e3 * no3 + elementsCountAround + en + 1
                nodeIdentifiers = [
                    bni11, bni12, bni21, bni22, bni11 + no3, bni12 + no3,
                    bni21 + no3, bni22 + no3
                ]
                result = element.setNodesByIdentifier(eftOuter,
                                                      nodeIdentifiers)
                rev = e1 >= (elementsCount1 + elementsCount2)
                one = -1.0 if rev else 1.0
                vx = one if onX else 0.0
                vy = 0.0 if onX else one
                scaleFactors = [
                    -1.0, 4.0, vx, vy, -outer_d2[e1][0] / outer_dx_ds1[0] /
                    elementsCountThroughWall, -outer_d2[e1][1] /
                    outer_dx_ds2[1] / elementsCountThroughWall, vx, vy,
                    -outer_d2[en][0] / outer_dx_ds1[0] /
                    elementsCountThroughWall, -outer_d2[en][1] /
                    outer_dx_ds2[1] / elementsCountThroughWall
                ]
                element.setScaleFactors(eftOuter, scaleFactors)
                elementIdentifier = elementIdentifier + 1

            # remaining rows
            for e2 in range(1, elementsCountThroughWall):
                for e1 in range(elementsCountAround):
                    element = mesh.createElement(elementIdentifier,
                                                 elementtemplate)
                    bni11 = e3 * no3 + e2 * elementsCountAround + e1 + 1
                    bni12 = e3 * no3 + e2 * elementsCountAround + (
                        e1 + 1) % elementsCountAround + 1
                    bni21 = e3 * no3 + (e2 + 1) * elementsCountAround + e1 + 1
                    bni22 = e3 * no3 + (e2 + 1) * elementsCountAround + (
                        e1 + 1) % elementsCountAround + 1
                    nodeIdentifiers = [
                        bni11, bni12, bni21, bni22, bni11 + no3, bni12 + no3,
                        bni21 + no3, bni22 + no3
                    ]
                    result = element.setNodesByIdentifier(eft, nodeIdentifiers)
                    elementIdentifier = elementIdentifier + 1

        fm.endChange()
def generateOstiumMesh(region, options, trackSurface, centrePosition, axis1, startNodeIdentifier = 1, startElementIdentifier = 1,
        vesselMeshGroups = None):
    '''
    :param vesselMeshGroups: List (over number of vessels) of list of mesh groups to add vessel elements to.
    :return: nextNodeIdentifier, nextElementIdentifier, Ostium points tuple
    (ox[n3][n1][c], od1[n3][n1][c], od2[n3][n1][c], od3[n3][n1][c], oNodeId[n3][n1], oPositions).
    '''
    vesselsCount = options['Number of vessels']
    elementsCountAroundOstium = options['Number of elements around ostium']
    elementsCountAcross = options['Number of elements across common']
    elementsCountsAroundVessels, elementsCountAroundMid = getOstiumElementsCountsAroundVessels(elementsCountAroundOstium, elementsCountAcross, vesselsCount)
    elementsCountAroundEnd = (elementsCountAroundOstium - 2*elementsCountAroundMid)//2
    #print('\nvesselsCount', vesselsCount, 'elementsCountsAroundOstium', elementsCountAroundOstium, 'elementsCountAcross', elementsCountAcross)
    #print('--> elementsCountsAroundVessels', elementsCountsAroundVessels, 'elementsCountAroundMid', elementsCountAroundMid)
    elementsCountAlong = options['Number of elements along']
    elementsCountThroughWall = options['Number of elements through wall']
    unitScale = options['Unit scale']

    isOutlet = options['Outlet']
    ostiumRadius = 0.5*unitScale*options['Ostium diameter']
    ostiumLength = unitScale*options['Ostium length']
    ostiumWallThickness = unitScale*options['Ostium wall thickness']
    interVesselHeight = unitScale*options['Ostium inter-vessel height']
    interVesselDistance = unitScale*options['Ostium inter-vessel distance'] if (vesselsCount > 1) else 0.0
    halfInterVesselDistance = 0.5*interVesselDistance
    useCubicHermiteThroughOstiumWall = not(options['Use linear through ostium wall'])
    vesselEndDerivative = ostiumLength*options['Vessel end length factor']/elementsCountAlong
    vesselInnerRadius = 0.5*unitScale*options['Vessel inner diameter']
    vesselWallThickness = unitScale*options['Vessel wall thickness']
    vesselOuterRadius = vesselInnerRadius + vesselWallThickness
    vesselAngle1Radians = math.radians(options['Vessel angle 1 degrees'])
    vesselAngle1SpreadRadians = math.radians(options['Vessel angle 1 spread degrees'])
    vesselAngle2Radians = math.radians(options['Vessel angle 2 degrees'])
    useCubicHermiteThroughVesselWall = not(options['Use linear through vessel wall'])
    useCrossDerivatives = False  # options['Use cross derivatives']  # not implemented

    fm = region.getFieldmodule()
    fm.beginChange()
    coordinates = findOrCreateFieldCoordinates(fm)
    cache = fm.createFieldcache()

    # track points in shape of ostium

    # get directions in plane of surface at centre:
    cx, cd1, cd2 = trackSurface.evaluateCoordinates(centrePosition, True)
    trackDirection1, trackDirection2, centreNormal = calculate_surface_axes(cd1, cd2, axis1)
    trackDirection2reverse = [ -d for d in trackDirection2 ]

    halfCircumference = math.pi*ostiumRadius
    circumference = 2.0*halfCircumference
    distance = 0.0
    elementLengthAroundOstiumMid = 0.0
    vesselsSpanAll = interVesselDistance*(vesselsCount - 1)
    vesselsSpanMid = interVesselDistance*(vesselsCount - 2)
    if vesselsCount == 1:
        elementLengthAroundOstiumEnd = circumference/elementsCountAroundOstium
        vesselOstiumPositions = [ centrePosition ]
        ocx  = [ cx ]
        ocd1 = [ trackDirection1 ]
        ocd2 = [ trackDirection2 ]
        ocd3 = [ centreNormal ]
    else:
        elementLengthAroundOstiumEnd = (circumference + 2.0*interVesselDistance)/(elementsCountAroundOstium - 2*elementsCountAroundMid)
        if elementsCountAroundMid > 0:
            elementLengthAroundOstiumMid = interVesselDistance*(vesselsCount - 2)/elementsCountAroundMid
        vesselOstiumPositions = []
        ocx  = []
        ocd1 = []
        ocd2 = []
        ocd3 = []
        for v in range(vesselsCount):
            vesselOstiumPositions.append(trackSurface.trackVector(centrePosition, trackDirection1, (v/(vesselsCount - 1) - 0.5)*vesselsSpanAll))
            x, d1, d2 = trackSurface.evaluateCoordinates(vesselOstiumPositions[-1], -1)
            d1, d2, d3 = calculate_surface_axes(d1, d2, trackDirection1)
            ocx .append(x)
            ocd1.append(d1)
            ocd2.append(d2)
            ocd3.append(d3)

    # coordinates around ostium
    ox = [ [], [] ]
    od1 = [ [], [] ]
    od2 = [ [], [] ]
    od3 = [ [], [] ]
    oPositions = []
    for n1 in range(elementsCountAroundOstium):
        elementLength = elementLengthAroundOstiumEnd
        if distance <= (vesselsSpanMid + halfInterVesselDistance):
            position = trackSurface.trackVector(centrePosition, trackDirection1, 0.5*vesselsSpanMid - distance)
            sideDirection = trackDirection2reverse
            if n1 < elementsCountAroundMid:
                elementLength = elementLengthAroundOstiumMid
        elif distance < (vesselsSpanMid + halfInterVesselDistance + halfCircumference):
            position = vesselOstiumPositions[0]
            angleRadians = (distance - (vesselsSpanMid + halfInterVesselDistance))/ostiumRadius
            w1 = -math.sin(angleRadians)
            w2 = -math.cos(angleRadians)
            sideDirection = [ (w1*trackDirection1[c] + w2*trackDirection2[c]) for c in range(3) ]
        elif distance < (2.0*vesselsSpanMid + halfInterVesselDistance + halfCircumference + interVesselDistance):
            position = trackSurface.trackVector(centrePosition, trackDirection1, distance - (1.5*vesselsSpanMid + interVesselDistance + halfCircumference))
            sideDirection = trackDirection2
            if 0 <= (n1 - elementsCountAroundEnd - elementsCountAroundMid) < elementsCountAroundMid:
                elementLength = elementLengthAroundOstiumMid
        elif distance < (2.0*vesselsSpanMid + halfInterVesselDistance + circumference + interVesselDistance):
            position = vesselOstiumPositions[-1]
            angleRadians = (distance - (2.0*vesselsSpanMid + halfInterVesselDistance + halfCircumference + interVesselDistance))/ostiumRadius
            w1 = math.sin(angleRadians)
            w2 = math.cos(angleRadians)
            sideDirection = [ (w1*trackDirection1[c] + w2*trackDirection2[c]) for c in range(3) ]
        else:
            position = trackSurface.trackVector(centrePosition, trackDirection1, 0.5*vesselsSpanMid + (circumference + 2.0*(vesselsSpanMid + interVesselDistance)) - distance)
            sideDirection = trackDirection2reverse
        position = trackSurface.trackVector(position, sideDirection, ostiumRadius)
        oPositions.append(position)
        px, d1, d2 = trackSurface.evaluateCoordinates(position, True)
        pd2, pd1, pd3 = calculate_surface_axes(d1, d2, sideDirection)
        # get outer coordinates
        opx = px
        opd1 = vector.setMagnitude([ -d for d in pd1 ], elementLengthAroundOstiumEnd)
        opd2 = vector.setMagnitude(pd2, elementLengthAroundOstiumEnd)  # smoothed later
        opd3 = vector.setMagnitude(pd3, ostiumWallThickness)
        # set inner and outer coordinates (use copy to avoid references to same list later)
        ox [0].append([ (opx[c] - opd3[c]) for c in range(3) ])
        od1[0].append(copy.copy(opd1))
        od2[0].append(copy.copy(opd2))
        ox [1].append(opx)
        od1[1].append(opd1)
        od2[1].append(opd2)
        if useCubicHermiteThroughOstiumWall:
            od3[0].append(copy.copy(opd3))
            od3[1].append(opd3)
        distance += elementLength
    for n3 in range(2):
        od1[n3] = interp.smoothCubicHermiteDerivativesLoop(ox[n3], od1[n3], fixAllDirections = True)

    xx  = []
    xd1 = []
    xd2 = []
    xd3 = []
    # coordinates across common ostium, between vessels
    nodesCountFreeEnd = elementsCountsAroundVessels[0] + 1 - elementsCountAcross
    oinc = 0 if (vesselsCount <= 2) else elementsCountAroundMid//(vesselsCount - 2)
    for iv in range(vesselsCount - 1):
        xx .append([ None, None ])
        xd1.append([ None, None ])
        xd2.append([ None, None ])
        xd3.append([ None, None ])
        oa = elementsCountAroundMid - iv*oinc
        ob = elementsCountAroundMid + nodesCountFreeEnd - 1 + iv*oinc
        nx = [ ox[1][oa], ox[1][ob] ]
        nd1 = [ [ -d for d in od1[1][oa] ], od1[1][ob] ]
        nd2 = [ [ -d for d in od2[1][oa] ], od2[1][ob] ]
        if elementsCountAcross > 1:
            # add centre point, displaced by interVesselHeight
            if vesselsCount == 2:
                position = centrePosition
            else:
                position = trackSurface.trackVector(centrePosition, trackDirection1, (iv/(vesselsCount - 2) - 0.5)*vesselsSpanMid)
            mx, d1, d2 = trackSurface.evaluateCoordinates(position, derivatives = True)
            md1, md2, md3 = calculate_surface_axes(d1, d2, trackDirection1)
            nx .insert(1, [ (mx[c] + interVesselHeight*md3[c]) for c in range(3) ])
            nd1.insert(1, vector.setMagnitude(md1, elementLengthAroundOstiumMid if (0 < iv < (vesselsCount - 2)) else elementLengthAroundOstiumEnd))
            nd2.insert(1, vector.setMagnitude(md2, ostiumRadius))
        nd2 = interp.smoothCubicHermiteDerivativesLine(nx, nd2, fixAllDirections = True)
        px, pd2, pe, pxi = interp.sampleCubicHermiteCurves(nx, nd2, elementsCountAcross)[0:4]
        pd1 = interp.interpolateSampleLinear(nd1, pe, pxi)
        pd3 = [ vector.setMagnitude(vector.crossproduct3(pd1[n2], pd2[n2]), ostiumWallThickness) for n2 in range(elementsCountAcross + 1) ]
        lx = [ ([ (px[n2][c] - pd3[n2][c]) for c in range(3) ]) for n2 in range(elementsCountAcross + 1) ]
        ld2 = interp.smoothCubicHermiteDerivativesLine(lx, pd2, fixAllDirections = True)
        xx [iv][0] = lx [1:elementsCountAcross]
        xd1[iv][0] = copy.deepcopy(pd1[1:elementsCountAcross])  # to be smoothed later
        xd2[iv][0] = ld2[1:elementsCountAcross]
        xx [iv][1] = px [1:elementsCountAcross]
        xd1[iv][1] = pd1[1:elementsCountAcross]  # to be smoothed later
        xd2[iv][1] = pd2[1:elementsCountAcross]
        if useCubicHermiteThroughOstiumWall:
            xd3[iv][0] = copy.deepcopy(pd3[1:elementsCountAcross])
            xd3[iv][1] = pd3[1:elementsCountAcross]
        # set smoothed d2 on ostium circumference
        od2[0][oa] = [ -d for d in ld2[0] ]
        od2[1][oa] = [ -d for d in pd2[0] ]
        od2[0][ob] = ld2[-1]
        od2[1][ob] = pd2[-1]

    # get positions of vessel end centres and rings
    vcx = []
    vcd1 = []
    vcd2 = []
    vcd3 = []
    vox = []
    vod1 = []
    vod2 = []
    vod3 = []
    for v in range(vesselsCount):
        elementsCountAroundVessel = elementsCountsAroundVessels[v]
        radiansPerElementVessel = 2.0*math.pi/elementsCountAroundVessel
        useVesselAngleRadians = vesselAngle1Radians
        if vesselsCount > 1:
            useVesselAngleRadians += (v/(vesselsCount - 1) - 0.5)*vesselAngle1SpreadRadians
        vx, vd1, vd2, vd3 = getCircleProjectionAxes(ocx[v], ocd1[v], ocd2[v], ocd3[v], ostiumLength, useVesselAngleRadians, vesselAngle2Radians)
        vd1 = [    vesselOuterRadius*d for d in vd1 ]
        vd2 = [   -vesselOuterRadius*d for d in vd2 ]
        vd3 = [ -vesselEndDerivative*d for d in vd3 ]
        vcx.append(vx)
        vcd1.append(vd1)
        vcd2.append(vd2)
        vcd3.append(vd3)
        vox.append([])
        vod1.append([])
        vod2.append([])
        vod3.append([])
        for n3 in range(2):
            radius = vesselInnerRadius if (n3 == 0) else vesselOuterRadius
            vAxis1 = vector.setMagnitude(vd1, radius)
            vAxis2 = vector.setMagnitude(vd2, radius)
            if vesselsCount == 1:
                startRadians = 0.5*math.pi
            else:
                startRadians = 0.5*radiansPerElementVessel*elementsCountAcross
                if v == (vesselsCount - 1):
                    startRadians -= math.pi
            px, pd1 = createCirclePoints(vx, vAxis1, vAxis2, elementsCountAroundVessel, startRadians)
            vox [-1].append(px)
            vod1[-1].append(pd1)
            vod2[-1].append([ vd3 ]*elementsCountAroundVessel)
            if useCubicHermiteThroughVesselWall:
                vod3[-1].append([ vector.setMagnitude(vector.crossproduct3(d1, vd3), vesselWallThickness) for d1 in pd1 ])

    # calculate common ostium vessel node derivatives map
    mvPointsx = [ None ]*vesselsCount
    mvPointsd1 = [ None ]*vesselsCount
    mvPointsd2 = [ None ]*vesselsCount
    mvPointsd3 = [ None ]*vesselsCount
    mvDerivativesMap = [ None ]*vesselsCount
    mvMeanCount = [ None ]*vesselsCount  # stores 1 if first reference to common point between vessels, 2 if second. Otherwise 0.
    for v in range(vesselsCount):
        if vesselsCount == 1:
            mvPointsx[v], mvPointsd1[v], mvPointsd2[v], mvPointsd3[v], mvDerivativesMap[v] = \
                ox, od1, od2, od3 if useCubicHermiteThroughOstiumWall else None, None
            mvMeanCount[v] = [ 0 ]*elementsCountsAroundVessels[v]
        else:
            iv = max(0, v - 1)
            oa = elementsCountAroundMid - iv*oinc
            ob = elementsCountAroundMid + nodesCountFreeEnd - 1 + iv*oinc
            mvPointsx [v] = []
            mvPointsd1[v] = []
            mvPointsd2[v] = []
            mvPointsd3[v] = [] if useCubicHermiteThroughOstiumWall else None
            mvDerivativesMap[v] = []
            for n3 in range(2):
                mvPointsd1[v].append([])
                mvPointsd2[v].append([])
                mvPointsx [v].append([])
                if useCubicHermiteThroughOstiumWall:
                    mvPointsd3[v].append([])
                mvDerivativesMap[v].append([])
                if v == 0:  # first end vessel
                    mvPointsd1[v][n3] += od1[n3][oa:ob + 1]
                    mvPointsd2[v][n3] += od2[n3][oa:ob + 1]
                    mvPointsx [v][n3] += ox [n3][oa:ob + 1]
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += od3[n3][oa:ob + 1]
                    mvDerivativesMap[v][n3].append( ( (0, 1, 0), (-1, 1, 0), None, (1, 0, 0) ) )
                    for i in range(nodesCountFreeEnd - 2):
                        mvDerivativesMap[v][n3].append( ( None, None, None ) )
                    mvDerivativesMap[v][n3].append( ( (1, 0, 0), (1, 1, 0), None, (0, -1, 0) ) )
                    mvPointsx [v][n3] += reversed(xx [iv][n3])
                    mvPointsd1[v][n3] += reversed(xd1[iv][n3])
                    mvPointsd2[v][n3] += reversed(xd2[iv][n3])
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += reversed(xd3[iv][n3])
                    for i in range(elementsCountAcross - 1):
                        mvDerivativesMap[v][n3].append( ( (0, -1, 0), (1, 0, 0), None ) )
                    if n3 == 0:
                        mvMeanCount[v] = [ 1 ] + [ 0 ]*(nodesCountFreeEnd - 2) + [ 1 ]*elementsCountAcross
                elif v < (vesselsCount - 1):  # middle vessels
                    # left:
                    mvPointsx [v][n3] += ox [n3][oa - oinc:oa + 1]
                    mvPointsd1[v][n3] += od1[n3][oa - oinc:oa + 1]
                    mvPointsd2[v][n3] += od2[n3][oa - oinc:oa + 1]
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += od3[n3][oa - oinc:oa + 1]
                    mvDerivativesMap[v][n3].append( ( (0, 1, 0), (-1, 1, 0), None, (1, 0, 0) ) )
                    for i in range(oinc - 1):
                        mvDerivativesMap[v][n3].append( ( None, None, None ) )
                    mvDerivativesMap[v][n3].append( ( (1, 0, 0), (1, 1, 0), None, (0, -1, 0) ) )
                    # across
                    mvPointsx [v][n3] += xx [iv][n3]
                    mvPointsd1[v][n3] += xd1[iv][n3]
                    mvPointsd2[v][n3] += xd2[iv][n3]
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += xd3[iv][n3]
                    for i in range(elementsCountAcross - 1):
                        mvDerivativesMap[v][n3].append( ( (0, 1, 0), (-1, 0, 0), None ) )
                    # right
                    mvPointsx [v][n3] += ox [n3][ob:ob + oinc + 1]
                    mvPointsd1[v][n3] += od1[n3][ob:ob + oinc + 1]
                    mvPointsd2[v][n3] += od2[n3][ob:ob + oinc + 1]
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += od3[n3][ob:ob + oinc + 1]
                    mvDerivativesMap[v][n3].append( ( (0, 1, 0), (-1, 1, 0), None, (1, 0, 0) ) )
                    for i in range(oinc - 1):
                        mvDerivativesMap[v][n3].append( ( None, None, None ) )
                    mvDerivativesMap[v][n3].append( ( (1, 0, 0), (1, 1, 0), None, (0, -1, 0) ) )
                    # across reverse
                    mvPointsx [v][n3] += reversed(xx [iv + 1][n3])
                    mvPointsd1[v][n3] += reversed(xd1[iv + 1][n3])
                    mvPointsd2[v][n3] += reversed(xd2[iv + 1][n3])
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += reversed(xd3[iv + 1][n3])
                    for i in range(elementsCountAcross - 1):
                        mvDerivativesMap[v][n3].append( ( (0, -1, 0), (1, 0, 0), None ) )
                    if n3 == 0:
                        mvMeanCount[v] = [ 1 ] + [ 0 ]*(oinc - 1) + [ 2 ]*(elementsCountAcross + 1) + [ 0 ]*(oinc - 1) + [ 1 ]*elementsCountAcross
                else:  # last end vessel
                    mvPointsx [v][n3] += ox [n3][ob:] + [ ox [n3][0] ]
                    mvPointsd1[v][n3] += od1[n3][ob:] + [ od1[n3][0] ]
                    mvPointsd2[v][n3] += od2[n3][ob:] + [ od2[n3][0] ]
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += od3[n3][ob:] + [ od3[n3][0] ]
                    mvDerivativesMap[v][n3].append( ( (0, 1, 0), (-1, 1, 0), None, (1, 0, 0) ) )
                    for i in range(nodesCountFreeEnd - 2):
                        mvDerivativesMap[v][n3].append( ( None, None, None ) )
                    mvDerivativesMap[v][n3].append( ( (1, 0, 0), (1, 1, 0), None, (0, -1, 0) ) )
                    mvPointsx [v][n3] += xx [iv][n3]
                    mvPointsd1[v][n3] += xd1[iv][n3]
                    mvPointsd2[v][n3] += xd2[iv][n3]
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += xd3[iv][n3]
                    for i in range(elementsCountAcross - 1):
                        mvDerivativesMap[v][n3].append( ( (0, 1, 0), (-1, 0, 0), None ) )
                    if n3 == 0:
                        mvMeanCount[v] = [ 2 ] + [ 0 ]*(nodesCountFreeEnd - 2) + [ 2 ]*elementsCountAcross

    # calculate derivative 2 around free sides of inlets to fit vessel derivatives
    for v in range(vesselsCount):
        for n3 in range(2):
            #print('v',v,'n3',n3,'elementsAround',elementsCountsAroundVessels[v])
            #print('mvPointsx [v][n3]', mvPointsx [v][n3])
            #print('mvPointsd1[v][n3]', mvPointsd1[v][n3])
            #print('mvPointsd2[v][n3]', mvPointsd2[v][n3])
            #print('mvDerivativesMap[v][n3]', mvDerivativesMap[v][n3])
            for n1 in range(elementsCountsAroundVessels[v]):
                d2Map = mvDerivativesMap[v][n3][n1][1] if (mvDerivativesMap[v] and mvDerivativesMap[v][n3][n1]) else None
                sf1 = d2Map[0] if d2Map else 0.0
                sf2 = d2Map[1] if d2Map else 1.0
                nx = [ vox[v][n3][n1], mvPointsx[v][n3][n1] ]
                nd2 = [ [ d*elementsCountAlong for d in vod2[v][n3][n1] ], [ (sf1*mvPointsd1[v][n3][n1][c] + sf2*mvPointsd2[v][n3][n1][c]) for c in range(3) ] ]
                nd2f = interp.smoothCubicHermiteDerivativesLine(nx, nd2, fixStartDerivative = True, fixEndDirection = True)
                ndf = [ d/elementsCountAlong for d in nd2f[1] ]
                # assign components to set original values:
                if sf1 == 0:
                    for c in range(3):
                        mvPointsd2[v][n3][n1][c] = sf2*ndf[c]
                elif sf2 == 0:
                    if mvMeanCount[v][n1] < 2:
                        for c in range(3):
                            mvPointsd1[v][n3][n1][c] = sf1*ndf[c]
                    else:
                        # take mean of values from this and last vessel
                        for c in range(3):
                            mvPointsd1[v][n3][n1][c] = 0.5*(mvPointsd1[v][n3][n1][c] + sf1*ndf[c])
                else:
                    #print('v', v, 'n3', n3, 'n1', n1, ':', vector.magnitude(ndf), 'vs.', vector.magnitude(nd2[1]), 'd2Map', d2Map)
                    pass

    if isOutlet:
        # reverse directions of d1 and d2 on vessels and ostium base
        for c in range(3):
            for n3 in range(2):
                for n1 in range(elementsCountAroundOstium):
                    od1[n3][n1][c] = -od1[n3][n1][c]
                    od2[n3][n1][c] = -od2[n3][n1][c]
                for iv in range(vesselsCount - 1):
                    for n1 in range(elementsCountAcross - 1):
                        xd1[iv][n3][n1][c] = -xd1[iv][n3][n1][c]
                        xd2[iv][n3][n1][c] = -xd2[iv][n3][n1][c]
                for v in range(vesselsCount):
                    for n1 in range(elementsCountsAroundVessels[v]):
                        vod1[v][n3][n1][c] = -vod1[v][n3][n1][c]
            # d2 is referenced all around, so only change once per vessel
            for v in range(vesselsCount):
                vod2[v][0][0][c] = -vod2[v][0][0][c]

    ##############
    # Create nodes
    ##############

    nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)

    nodetemplate = nodes.createNodetemplate()
    nodetemplate.defineField(coordinates)
    nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_VALUE, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS1, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS2, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS3, 1)
    nodetemplateLinearS3 = nodes.createNodetemplate()
    nodetemplateLinearS3.defineField(coordinates)
    nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_VALUE, 1)
    nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS1, 1)
    nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS2, 1)

    nodeIdentifier = startNodeIdentifier

    oNodeId = []
    for n3 in range(2):
        oNodeId.append([])
        for n1 in range(elementsCountAroundOstium):
            node = nodes.createNode(nodeIdentifier, nodetemplate if useCubicHermiteThroughOstiumWall else nodetemplateLinearS3)
            cache.setNode(node)
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, ox [n3][n1])
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, od1[n3][n1])
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, od2[n3][n1])
            if useCubicHermiteThroughOstiumWall:
                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, od3[n3][n1])
            oNodeId[n3].append(nodeIdentifier)
            nodeIdentifier += 1

    xNodeId = []
    for iv in range(vesselsCount - 1):
        xNodeId.append([])
        for n3 in range(2):
            xNodeId[iv].append([])
            for n2 in range(elementsCountAcross - 1):
                node = nodes.createNode(nodeIdentifier, nodetemplate if useCubicHermiteThroughOstiumWall else nodetemplateLinearS3)
                cache.setNode(node)
                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, xx [iv][n3][n2])
                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, xd1[iv][n3][n2])
                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, xd2[iv][n3][n2])
                if useCubicHermiteThroughOstiumWall:
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, xd3[iv][n3][n2])
                xNodeId[iv][n3].append(nodeIdentifier)
                nodeIdentifier += 1

    #for v in range(vesselsCount):
    #    node = nodes.createNode(nodeIdentifier, nodetemplate)
    #    cache.setNode(node)
    #    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, vcx [v])
    #    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, vcd1[v])
    #    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, vcd2[v])
    #    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, vcd3[v])
    #    nodeIdentifier += 1
    #    for n3 in range(2):
    #        for n1 in range(elementsCountsAroundVessels[v]):
    #            node = nodes.createNode(nodeIdentifier, nodetemplate if useCubicHermiteThroughVesselWall else nodetemplateLinearS3)
    #            cache.setNode(node)
    #            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, vox [v][n3][n1])
    #            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, vod1[v][n3][n1])
    #            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, vod2[v][n3][n1])
    #            if useCubicHermiteThroughVesselWall:
    #                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, vod3[v][n3][n1])
    #            #vNodeId.append(nodeIdentifier)
    #            nodeIdentifier += 1

    # get identifiers of nodes around each vessel at ostium end
    mvNodeId = [ None ]*vesselsCount
    for v in range(vesselsCount):
        if vesselsCount == 1:
            mvNodeId[v] = oNodeId
        else:
            iv = max(0, v - 1)
            mvNodeId[v] = [ None, None ]
            oa = elementsCountAroundMid - iv*oinc
            ob = elementsCountAroundMid + nodesCountFreeEnd - 1 + iv*oinc
            for n3 in range(2):
                if v == 0:  # first end vessel
                    mvNodeId[v][n3] = oNodeId[n3][oa:ob + 1] + (list(reversed(xNodeId[iv][n3])) if (v == 0) else xNodeId[iv][n3])
                elif v == (vesselsCount - 1):  # last end vessels
                    mvNodeId[v][n3] = oNodeId[n3][ob:] + [ oNodeId[n3][0] ] + (list(reversed(xNodeId[iv][n3])) if (v == 0) else xNodeId[iv][n3])
                else:  # mid vessels
                    mvNodeId[v][n3] = oNodeId[n3][oa - oinc:oa + 1] + xNodeId[iv][n3] + oNodeId[n3][ob:ob + oinc + 1] + list(reversed(xNodeId[iv + 1][n3]))

    #################
    # Create elementss
    #################

    mesh = fm.findMeshByDimension(3)
    elementIdentifier = startElementIdentifier

    tricubichermite = eftfactory_tricubichermite(mesh, useCrossDerivatives)
    #tricubicHermiteBasis = fm.createElementbasis(3, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE)

    #eft = tricubichermite.createEftBasic()
    #elementtemplate = mesh.createElementtemplate()
    #elementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
    #elementtemplate.defineField(coordinates, -1, eft)

    #elementtemplateX = mesh.createElementtemplate()
    #elementtemplateX.setElementShapeType(Element.SHAPE_TYPE_CUBE)

    for v in range(vesselsCount):
        if isOutlet:
            startPointsx, startPointsd1, startPointsd2, startPointsd3, startNodeId, startDerivativesMap = \
                mvPointsx[v], mvPointsd1[v], mvPointsd2[v], mvPointsd3[v], mvNodeId[v], mvDerivativesMap[v]
            endPointsx, endPointsd1, endPointsd2, endPointsd3, endNodeId, endDerivativesMap = \
                vox[v], vod1[v], vod2[v], vod3[v] if useCubicHermiteThroughVesselWall else None, None, None
            # reverse order of nodes around:
            for px in [ startPointsx, startPointsd1, startPointsd2, startPointsd3, startNodeId, startDerivativesMap, \
                        endPointsx, endPointsd1, endPointsd2, endPointsd3, endNodeId, endDerivativesMap ]:
                if px:
                    for n3 in range(2):
                        px[n3] = [ px[n3][0] ] + px[n3][len(px[n3]) - 1:0:-1]
            if vesselsCount > 1:
                # must switch in and out xi1 maps around corners in startDerivativesMap
                for n3 in range(2):
                    for n1 in range(elementsCountsAroundVessels[v]):
                        derivativesMap = startDerivativesMap[n3][n1]
                        if len(derivativesMap) == 4:
                            startDerivativesMap[n3][n1] = derivativesMap[3], derivativesMap[1], derivativesMap[2], derivativesMap[0]
        else:
            startPointsx, startPointsd1, startPointsd2, startPointsd3, startNodeId, startDerivativesMap = \
                vox[v], vod1[v], vod2[v], vod3[v] if useCubicHermiteThroughVesselWall else None, None, None
            endPointsx, endPointsd1, endPointsd2, endPointsd3, endNodeId, endDerivativesMap = \
                mvPointsx[v], mvPointsd1[v], mvPointsd2[v], mvPointsd3[v], mvNodeId[v], mvDerivativesMap[v]
        #print('endPointsx ', endPointsx )
        #print('endPointsd1', endPointsd1)
        #print('endPointsd2', endPointsd2)
        #print('endPointsd3', endPointsd3)
        #print('endNodeId', endNodeId)
        #print('endDerivativesMap', endDerivativesMap)
        nodeIdentifier, elementIdentifier = createAnnulusMesh3d(
            nodes, mesh, nodeIdentifier, elementIdentifier,
            startPointsx, startPointsd1, startPointsd2, startPointsd3, startNodeId, startDerivativesMap,
            endPointsx, endPointsd1, endPointsd2, endPointsd3, endNodeId, endDerivativesMap,
            forceMidLinearXi3 = not useCubicHermiteThroughVesselWall,
            elementsCountRadial = elementsCountAlong,
            meshGroups = vesselMeshGroups[v] if vesselMeshGroups else [])

    fm.endChange()
    return nodeIdentifier, elementIdentifier, (ox, od1, od2, od3, oNodeId, oPositions)
Beispiel #8
0
def createNodesAndElements(region, x, d1, d2, d3, xFlat, d1Flat, d2Flat,
                           xTexture, d1Texture, d2Texture, elementsCountAround,
                           elementsCountAlong, elementsCountThroughWall,
                           annotationGroups, annotationArray,
                           firstNodeIdentifier, firstElementIdentifier,
                           useCubicHermiteThroughWall, useCrossDerivatives):
    """
    Create nodes and elements for the coordinates, flat coordinates,
    and texture coordinates field.
    :param x, d1, d2, d3: coordinates and derivatives of coordinates field.
    :param xFlat, d1Flat, d2Flat, d3Flat: coordinates and derivatives of
    flat coordinates field.
    :param xTexture, d1Texture, d2Texture, d3Texture: coordinates and derivatives
    of texture coordinates field.
    :param elementsCountAround: Number of elements around tube.
    :param elementsCountAlong: Number of elements along tube.
    :param elementsCountThroughWall: Number of elements through wall.
    :param annotationGroups: stores information about annotation groups.
    :param annotationArray: stores annotation names of elements around.
    :param firstNodeIdentifier, firstElementIdentifier: first node and
    element identifier to use.
    :param useCubicHermiteThroughWall: use linear when false
    :param useCrossDerivatives: use cross derivatives when true
    :return nodeIdentifier, elementIdentifier, annotationGroups
    """

    nodeIdentifier = firstNodeIdentifier
    elementIdentifier = firstElementIdentifier
    zero = [0.0, 0.0, 0.0]

    fm = region.getFieldmodule()
    fm.beginChange()
    cache = fm.createFieldcache()

    # Coordinates field
    coordinates = findOrCreateFieldCoordinates(fm)
    nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
    nodetemplate = nodes.createNodetemplate()
    nodetemplate.defineField(coordinates)
    nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                          Node.VALUE_LABEL_VALUE, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                          Node.VALUE_LABEL_D_DS1, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                          Node.VALUE_LABEL_D_DS2, 1)
    if useCrossDerivatives:
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D2_DS1DS2, 1)
    if useCubicHermiteThroughWall:
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS3, 1)
        if useCrossDerivatives:
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS1DS3,
                                                  1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS2DS3,
                                                  1)
            nodetemplate.setValueNumberOfVersions(
                coordinates, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1)

    mesh = fm.findMeshByDimension(3)

    if useCubicHermiteThroughWall:
        eftfactory = eftfactory_tricubichermite(mesh, useCrossDerivatives)
    else:
        eftfactory = eftfactory_bicubichermitelinear(mesh, useCrossDerivatives)
    eft = eftfactory.createEftBasic()

    elementtemplate = mesh.createElementtemplate()
    elementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
    result = elementtemplate.defineField(coordinates, -1, eft)

    # Flat coordinates field
    bicubichermitelinear = eftfactory_bicubichermitelinear(
        mesh, useCrossDerivatives)
    eftTexture1 = bicubichermitelinear.createEftBasic()
    eftTexture2 = bicubichermitelinear.createEftOpenTube()

    flatCoordinates = findOrCreateFieldCoordinates(fm, name="flat coordinates")
    flatNodetemplate1 = nodes.createNodetemplate()
    flatNodetemplate1.defineField(flatCoordinates)
    flatNodetemplate1.setValueNumberOfVersions(flatCoordinates, -1,
                                               Node.VALUE_LABEL_VALUE, 1)
    flatNodetemplate1.setValueNumberOfVersions(flatCoordinates, -1,
                                               Node.VALUE_LABEL_D_DS1, 1)
    flatNodetemplate1.setValueNumberOfVersions(flatCoordinates, -1,
                                               Node.VALUE_LABEL_D_DS2, 1)
    if useCrossDerivatives:
        flatNodetemplate1.setValueNumberOfVersions(flatCoordinates, -1,
                                                   Node.VALUE_LABEL_D2_DS1DS2,
                                                   1)

    flatNodetemplate2 = nodes.createNodetemplate()
    flatNodetemplate2.defineField(flatCoordinates)
    flatNodetemplate2.setValueNumberOfVersions(flatCoordinates, -1,
                                               Node.VALUE_LABEL_VALUE, 2)
    flatNodetemplate2.setValueNumberOfVersions(flatCoordinates, -1,
                                               Node.VALUE_LABEL_D_DS1, 2)
    flatNodetemplate2.setValueNumberOfVersions(flatCoordinates, -1,
                                               Node.VALUE_LABEL_D_DS2, 2)
    if useCrossDerivatives:
        flatNodetemplate2.setValueNumberOfVersions(flatCoordinates, -1,
                                                   Node.VALUE_LABEL_D2_DS1DS2,
                                                   2)

    flatElementtemplate1 = mesh.createElementtemplate()
    flatElementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE)
    flatElementtemplate1.defineField(flatCoordinates, -1, eftTexture1)

    flatElementtemplate2 = mesh.createElementtemplate()
    flatElementtemplate2.setElementShapeType(Element.SHAPE_TYPE_CUBE)
    flatElementtemplate2.defineField(flatCoordinates, -1, eftTexture2)

    # Texture coordinates field
    textureCoordinates = findOrCreateFieldTextureCoordinates(fm)
    textureNodetemplate1 = nodes.createNodetemplate()
    textureNodetemplate1.defineField(textureCoordinates)
    textureNodetemplate1.setValueNumberOfVersions(textureCoordinates, -1,
                                                  Node.VALUE_LABEL_VALUE, 1)
    textureNodetemplate1.setValueNumberOfVersions(textureCoordinates, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1)
    textureNodetemplate1.setValueNumberOfVersions(textureCoordinates, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1)
    if useCrossDerivatives:
        textureNodetemplate1.setValueNumberOfVersions(
            textureCoordinates, -1, Node.VALUE_LABEL_D2_DS1DS2, 1)

    textureNodetemplate2 = nodes.createNodetemplate()
    textureNodetemplate2.defineField(textureCoordinates)
    textureNodetemplate2.setValueNumberOfVersions(textureCoordinates, -1,
                                                  Node.VALUE_LABEL_VALUE, 2)
    textureNodetemplate2.setValueNumberOfVersions(textureCoordinates, -1,
                                                  Node.VALUE_LABEL_D_DS1, 2)
    textureNodetemplate2.setValueNumberOfVersions(textureCoordinates, -1,
                                                  Node.VALUE_LABEL_D_DS2, 2)
    if useCrossDerivatives:
        textureNodetemplate2.setValueNumberOfVersions(
            textureCoordinates, -1, Node.VALUE_LABEL_D2_DS1DS2, 2)

    elementtemplate1 = mesh.createElementtemplate()
    elementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE)
    elementtemplate1.defineField(textureCoordinates, -1, eftTexture1)

    elementtemplate2 = mesh.createElementtemplate()
    elementtemplate2.setElementShapeType(Element.SHAPE_TYPE_CUBE)
    elementtemplate2.defineField(textureCoordinates, -1, eftTexture2)

    # Create nodes
    # Coordinates field
    for n in range(len(x)):
        node = nodes.createNode(nodeIdentifier, nodetemplate)
        cache.setNode(node)
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1,
                                      x[n])
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1,
                                      d1[n])
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1,
                                      d2[n])
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1,
                                      d3[n])
        if useCrossDerivatives:
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D2_DS1DS2, 1, zero)
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D2_DS1DS3, 1, zero)
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D2_DS2DS3, 1, zero)
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D3_DS1DS2DS3, 1,
                                          zero)
        # print('NodeIdentifier = ', nodeIdentifier, xList[n])
        nodeIdentifier = nodeIdentifier + 1

    # Flat and texture coordinates fields
    nodeIdentifier = firstNodeIdentifier
    for n2 in range(elementsCountAlong + 1):
        for n3 in range(elementsCountThroughWall + 1):
            for n1 in range(elementsCountAround):
                i = n2 * (elementsCountAround +
                          1) * (elementsCountThroughWall +
                                1) + (elementsCountAround + 1) * n3 + n1
                node = nodes.findNodeByIdentifier(nodeIdentifier)
                node.merge(flatNodetemplate2 if n1 == 0 else flatNodetemplate1)
                node.merge(textureNodetemplate2 if n1 ==
                           0 else textureNodetemplate1)
                cache.setNode(node)
                # print('NodeIdentifier', nodeIdentifier, 'version 1, xList Index =', i+1)
                flatCoordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_VALUE, 1,
                                                  xFlat[i])
                flatCoordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1,
                                                  d1Flat[i])
                flatCoordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1,
                                                  d2Flat[i])
                textureCoordinates.setNodeParameters(cache, -1,
                                                     Node.VALUE_LABEL_VALUE, 1,
                                                     xTexture[i])
                textureCoordinates.setNodeParameters(cache, -1,
                                                     Node.VALUE_LABEL_D_DS1, 1,
                                                     d1Texture[i])
                textureCoordinates.setNodeParameters(cache, -1,
                                                     Node.VALUE_LABEL_D_DS2, 1,
                                                     d2Texture[i])
                if useCrossDerivatives:
                    flatCoordinates.setNodeParameters(
                        cache, -1, Node.VALUE_LABEL_D2_DS1DS2, 1, zero)
                    textureCoordinates.setNodeParameters(
                        cache, -1, Node.VALUE_LABEL_D2_DS1DS2, 1, zero)
                if n1 == 0:
                    # print('NodeIdentifier', nodeIdentifier, 'version 2, xList Index =', i+elementsCountAround+1)
                    flatCoordinates.setNodeParameters(
                        cache, -1, Node.VALUE_LABEL_VALUE, 2,
                        xFlat[i + elementsCountAround])
                    flatCoordinates.setNodeParameters(
                        cache, -1, Node.VALUE_LABEL_D_DS1, 2,
                        d1Flat[i + elementsCountAround])
                    flatCoordinates.setNodeParameters(
                        cache, -1, Node.VALUE_LABEL_D_DS2, 2,
                        d2Flat[i + elementsCountAround])
                    textureCoordinates.setNodeParameters(
                        cache, -1, Node.VALUE_LABEL_VALUE, 2,
                        xTexture[i + elementsCountAround])
                    textureCoordinates.setNodeParameters(
                        cache, -1, Node.VALUE_LABEL_D_DS1, 2,
                        d1Texture[i + elementsCountAround])
                    textureCoordinates.setNodeParameters(
                        cache, -1, Node.VALUE_LABEL_D_DS2, 2,
                        d2Texture[i + elementsCountAround])
                    if useCrossDerivatives:
                        flatCoordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D2_DS1DS2, 2, zero)
                        textureCoordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D2_DS1DS2, 2, zero)
                nodeIdentifier = nodeIdentifier + 1

    # create elements
    now = elementsCountAround * (elementsCountThroughWall + 1)
    for e2 in range(elementsCountAlong):
        for e3 in range(elementsCountThroughWall):
            for e1 in range(elementsCountAround):
                bni11 = e2 * now + e3 * elementsCountAround + e1 + 1
                bni12 = e2 * now + e3 * elementsCountAround + (
                    e1 + 1) % elementsCountAround + 1
                bni21 = e2 * now + (e3 + 1) * elementsCountAround + e1 + 1
                bni22 = e2 * now + (e3 + 1) * elementsCountAround + (
                    e1 + 1) % elementsCountAround + 1
                nodeIdentifiers = [
                    bni11, bni12, bni11 + now, bni12 + now, bni21, bni22,
                    bni21 + now, bni22 + now
                ]
                onOpening = e1 > elementsCountAround - 2
                element = mesh.createElement(elementIdentifier,
                                             elementtemplate)
                element.setNodesByIdentifier(eft, nodeIdentifiers)
                element.merge(flatElementtemplate2
                              if onOpening else flatElementtemplate1)
                element.merge(
                    elementtemplate2 if onOpening else elementtemplate1)
                element.setNodesByIdentifier(
                    eftTexture2 if onOpening else eftTexture1, nodeIdentifiers)
                elementIdentifier = elementIdentifier + 1
                if annotationGroups:
                    for annotationGroup in annotationGroups:
                        if annotationArray[e1] == annotationGroup._name:
                            meshGroup = annotationGroup.getMeshGroup(mesh)
                            meshGroup.addElement(element)

    fm.endChange()

    return nodeIdentifier, elementIdentifier, annotationGroups
Beispiel #9
0
    def generateMesh(region, options):
        """
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: None
        """
        elementsCountUp = options['Number of elements up']
        elementsCountUpSeptum = options['Number of elements up septum']
        elementsCountAround = options['Number of elements around']
        #print('elementsCountAround', elementsCountAround)
        elementsCountAlongSeptum = options['Number of extra elements along septum']
        atriaAxisRadians = math.radians(options['Major axis rotation degrees'])
        totalArcUpRadians = math.radians(options['Total arc up degrees'])
        septumArcUpRadians = totalArcUpRadians*options['Septum arc up ratio']
        lengthRatio = options['Length ratio']
        baseSeptumThickness = options['Base septum thickness']
        freeWallThickness = options['Free wall thickness']
        majorAxisRadians = math.radians(options['Major axis rotation degrees'])
        baseToEquatorRatio = 1.0/math.sin(totalArcUpRadians)
        innerMajorMag = 0.5*options['Base inner major axis length']
        innerMinorMag = 0.5*options['Base inner minor axis length']
        vcInnerDiameter = options['Vena cava inner diameter']
        vcWallThickness = options['Vena cava wall thickness']
        pvInnerDiameter = options['Pulmonary vein inner diameter']
        pvWallThickness = options['Pulmonary vein wall thickness']
        useCrossDerivatives = options['Use cross derivatives']

        fm = region.getFieldmodule()
        fm.beginChange()
        coordinates = getOrCreateCoordinateField(fm)

        cache = fm.createFieldcache()

        nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
        nodetemplateApex = nodes.createNodetemplate()
        nodetemplateApex.defineField(coordinates)
        nodetemplateApex.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_VALUE, 1)
        nodetemplateApex.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS1, 1)
        nodetemplateApex.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS2, 1)
        nodetemplateApex.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS3, 1)
        if useCrossDerivatives:
            nodetemplate = nodes.createNodetemplate()
            nodetemplate.defineField(coordinates)
            nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_VALUE, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS1, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS2, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D2_DS1DS2, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS3, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D2_DS1DS3, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D2_DS2DS3, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1)
        else:
            nodetemplate = nodetemplateApex

        ##############
        # Create nodes
        ##############

        nodeIdentifier = 1

        outerMajorMag = innerMajorMag + freeWallThickness
        outerMinorMag = innerMinorMag + freeWallThickness

        laInnerMajorX = innerMajorMag*math.sin(majorAxisRadians)
        laInnerMajorY = innerMajorMag*math.cos(majorAxisRadians)
        laInnerMinorX = -innerMinorMag*math.cos(majorAxisRadians)
        laInnerMinorY = innerMinorMag*math.sin(majorAxisRadians)

        laOuterMajorX = outerMajorMag*math.sin(majorAxisRadians)
        laOuterMajorY = outerMajorMag*math.cos(majorAxisRadians)
        laOuterMinorX = -outerMinorMag*math.cos(majorAxisRadians)
        laOuterMinorY = outerMinorMag*math.sin(majorAxisRadians)

        # following is radians around LA
        laSeptumRadians = math.atan2(laInnerMinorY, laInnerMajorY)
        laSeptumX = 0.0
        laSeptumY = -0.5*baseSeptumThickness
        laCentreX = laSeptumX - laInnerMajorX*math.cos(laSeptumRadians) - laInnerMinorX*math.sin(laSeptumRadians)
        laCentreY = laSeptumY - laInnerMajorY*math.cos(laSeptumRadians) - laInnerMinorY*math.sin(laSeptumRadians)

        raSeptumX = 0.0
        raSeptumY = 0.5*baseSeptumThickness

        raSeptumRadians = math.pi*2.0 - laSeptumRadians
        raCentreX = laCentreX
        raCentreY = -laCentreY

        raInnerMajorX = innerMajorMag*math.sin(majorAxisRadians)
        raInnerMajorY = -innerMajorMag*math.cos(majorAxisRadians)
        raInnerMinorX = innerMinorMag*math.cos(majorAxisRadians)
        raInnerMinorY = innerMinorMag*math.sin(majorAxisRadians)

        raOuterMajorX = outerMajorMag*math.sin(majorAxisRadians)
        raOuterMajorY = -outerMajorMag*math.cos(majorAxisRadians)
        raOuterMinorX = outerMinorMag*math.cos(majorAxisRadians)
        raOuterMinorY = outerMinorMag*math.sin(majorAxisRadians)

        laRadians = [0.0]*elementsCountAround
        laRadians[0] = laSeptumRadians
        deltaRadians = 2.0*math.pi/elementsCountAround
        laDeltaRadians = [ deltaRadians ]*elementsCountAround
        for i in range(1, elementsCountAround):
            laRadians[i] = laRadians[i - 1] + deltaRadians

        raRadians = [0.0]*elementsCountAround
        raRadians[0] = raSeptumRadians
        raDeltaRadians = [ deltaRadians ]*elementsCountAround
        for i in range(1, elementsCountAround):
            raRadians[i] = raRadians[i - 1] + deltaRadians

        upRadians = [ [ 0.0 ]*(elementsCountUp +  1), [ 0.0 ]*(elementsCountUp +  1) ]
        deltaUpRadians = [ [ 0.0 ]*(elementsCountUp +  1), [ 0.0 ]*(elementsCountUp +  1) ]
        baseRadiansUp = math.pi - totalArcUpRadians
        deltaRadians1 = septumArcUpRadians/elementsCountUpSeptum
        deltaRadiansN = (totalArcUpRadians - septumArcUpRadians - 0.5*deltaRadians1) / (elementsCountUp - elementsCountUpSeptum - 0.5)
        for i in range(elementsCountUpSeptum + 1):
            upRadians[0][i] = baseRadiansUp + i*deltaRadians1
            deltaUpRadians[0][i] = deltaRadians1
        for i in range(elementsCountUp - elementsCountUpSeptum):
            upRadians[0][elementsCountUp - i] = math.pi - i*deltaRadiansN
            deltaUpRadians[0][elementsCountUp - i] = deltaRadiansN
        # GRC temp: should make separate range to get inlet incline at bottom:
        for i in range(elementsCountUp + 1):
            upRadians[1][i] = upRadians[0][i]
            deltaUpRadians[1][i] = deltaUpRadians[0][i]

        #print('upRadians',upRadians[0])
        #print('deltaUpRadians',deltaUpRadians[0])

        innerScaleZ = (lengthRatio - freeWallThickness)/(1.0 - math.cos(totalArcUpRadians))
        outerScaleZ = lengthRatio/(1.0 - math.cos(totalArcUpRadians))

        laNodeId = [ [], [] ]
        raNodeId = [ [], [] ]
        laApexNodeId = [ -1 ]*2
        raApexNodeId = [ -1 ]*2

        for n3 in range(2):
            for n2 in range(elementsCountUp):

                # GRC support separate inner and outer radians up
                radiansUp = upRadians[n3][n2]
                scalingUp = baseToEquatorRatio*math.sin(radiansUp)
                innerZ = -innerScaleZ*math.cos(radiansUp)
                outerZ = -outerScaleZ*math.cos(radiansUp)

                laLayerNodeId = [-1]*elementsCountAround
                laNodeId[n3].append(laLayerNodeId)
                raLayerNodeId = [-1]*elementsCountAround
                raNodeId[n3].append(raLayerNodeId)

                # regular nodes up atria
                for i in range(2):
                    if i == 0:
                        # left
                        centreX, centreY = laCentreX, laCentreY
                        aRadians = laRadians
                        aDeltaRadians = laDeltaRadians
                        layerNodeId = laLayerNodeId
                        innerMajorX, innerMajorY = laInnerMajorX, laInnerMajorY
                        innerMinorX, innerMinorY = laInnerMinorX, laInnerMinorY
                        outerMajorX, outerMajorY = laOuterMajorX, laOuterMajorY
                        outerMinorX, outerMinorY = laOuterMinorX, laOuterMinorY
                    else:
                        # right
                        centreX, centreY = raCentreX, raCentreY
                        aRadians = raRadians
                        aDeltaRadians = raDeltaRadians
                        layerNodeId = raLayerNodeId
                        innerMajorX, innerMajorY = raInnerMajorX, raInnerMajorY
                        innerMinorX, innerMinorY = raInnerMinorX, raInnerMinorY
                        outerMajorX, outerMajorY = raOuterMajorX, raOuterMajorY
                        outerMinorX, outerMinorY = raOuterMinorX, raOuterMinorY

                    for n1 in range(elementsCountAround):
                        radiansAround = aRadians[n1]
                        cosRadiansAround = math.cos(radiansAround)
                        sinRadiansAround = math.sin(radiansAround)
                        inner = [
                            centreX + scalingUp*(cosRadiansAround*innerMajorX + sinRadiansAround*innerMinorX),
                            centreY + scalingUp*(cosRadiansAround*innerMajorY + sinRadiansAround*innerMinorY),
                            innerZ ]
                        outer = [
                            centreX + scalingUp*(cosRadiansAround*outerMajorX + sinRadiansAround*outerMinorX),
                            centreY + scalingUp*(cosRadiansAround*outerMajorY + sinRadiansAround*outerMinorY),
                            outerZ ]
                        if (n3 == 1) and (n2 <= elementsCountUpSeptum) and (n1 == 0):
                            continue  # right septum node created in next loop
                        node = nodes.createNode(nodeIdentifier, nodetemplate)
                        layerNodeId[n1] = nodeIdentifier
                        cache.setNode(node)
                        result = coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, inner if (n3 == 0) else outer)
                        if n3 == 0:
                            dx_ds1 = [
                                scalingUp*aDeltaRadians[n1]*(-sinRadiansAround*innerMajorX + cosRadiansAround*innerMinorX),
                                scalingUp*aDeltaRadians[n1]*(-sinRadiansAround*innerMajorY + cosRadiansAround*innerMinorY),
                                0.0 ]
                            dx_ds2 = [
                                deltaUpRadians[n3][n2]*math.cos(radiansUp)*(cosRadiansAround*innerMajorX + sinRadiansAround*innerMinorX),
                                deltaUpRadians[n3][n2]*math.cos(radiansUp)*(cosRadiansAround*innerMajorY + sinRadiansAround*innerMinorY),
                                deltaUpRadians[n3][n2]*math.sin(radiansUp)*innerScaleZ ]
                        else:
                            dx_ds1 = [
                                scalingUp*aDeltaRadians[n1]*(-sinRadiansAround*outerMajorX + cosRadiansAround*outerMinorX),
                                scalingUp*aDeltaRadians[n1]*(-sinRadiansAround*outerMajorY + cosRadiansAround*outerMinorY),
                                0.0 ]
                            dx_ds2 = [
                                deltaUpRadians[n3][n2]*math.cos(radiansUp)*(cosRadiansAround*outerMajorX + sinRadiansAround*outerMinorX),
                                deltaUpRadians[n3][n2]*math.cos(radiansUp)*(cosRadiansAround*outerMajorY + sinRadiansAround*outerMinorY),
                                deltaUpRadians[n3][n2]*math.sin(radiansUp)*outerScaleZ ]
                        dx_ds3 = [ outer[0] - inner[0], outer[1] - inner[1], outer[2] - inner[2] ]
                        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, dx_ds1)
                        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, dx_ds2)
                        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, dx_ds3)
                        nodeIdentifier += 1

            # apexes
            for i in range(2):
                if i == 0:
                    # left
                    centreX, centreY = laCentreX, laCentreY
                    apexNodeId = laApexNodeId
                    innerMajorX, innerMajorY = laInnerMajorX, laInnerMajorY
                    innerMinorX, innerMinorY = laInnerMinorX, laInnerMinorY
                    outerMajorX, outerMajorY = laOuterMajorX, laOuterMajorY
                    outerMinorX, outerMinorY = laOuterMinorX, laOuterMinorY
                else:
                    # right
                    centreX, centreY = raCentreX, raCentreY
                    aRadians = raRadians
                    apexNodeId = raApexNodeId
                    innerMajorX, innerMajorY = raInnerMajorX, raInnerMajorY
                    innerMinorX, innerMinorY = raInnerMinorX, raInnerMinorY
                    outerMajorX, outerMajorY = raOuterMajorX, raOuterMajorY
                    outerMinorX, outerMinorY = raOuterMinorX, raOuterMinorY

                x = [ centreX, centreY, innerScaleZ if (n3 == 0) else outerScaleZ ]
                if n3 == 0:
                    dx_ds1 = [
                        baseToEquatorRatio*deltaUpRadians[n3][-1]*innerMajorX,
                        baseToEquatorRatio*deltaUpRadians[n3][-1]*innerMajorY,
                        0.0 ]
                    dx_ds2 = [
                        baseToEquatorRatio*deltaUpRadians[n3][-1]*innerMinorX,
                        baseToEquatorRatio*deltaUpRadians[n3][-1]*innerMinorY,
                        0.0 ]
                else:
                    dx_ds1 = [
                        baseToEquatorRatio*deltaUpRadians[n3][-1]*outerMajorX,
                        baseToEquatorRatio*deltaUpRadians[n3][-1]*outerMajorY,
                        0.0 ]
                    dx_ds2 = [
                        baseToEquatorRatio*deltaUpRadians[n3][-1]*outerMinorX,
                        baseToEquatorRatio*deltaUpRadians[n3][-1]*outerMinorY,
                        0.0 ]
                dx_ds3 = [ 0.0, 0.0, freeWallThickness ]
                node = nodes.createNode(nodeIdentifier, nodetemplate)
                apexNodeId[n3] = nodeIdentifier
                cache.setNode(node)
                result = coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, x)
                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, dx_ds1)
                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, dx_ds2)
                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, dx_ds3)
                nodeIdentifier += 1

        # transfer inner septum nodes to outer on opposite side, set derivative 3 to be node difference
        for n2 in range(elementsCountUpSeptum + 1):
            laNodeId[1][n2][0] = raNodeId[0][n2][0]
            raNodeId[1][n2][0] = laNodeId[0][n2][0]
            node2 = nodes.findNodeByIdentifier(laNodeId[1][n2][0])
            cache.setNode(node2)
            result, x_o = coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, 3)
            node1 = nodes.findNodeByIdentifier(laNodeId[0][n2][0])
            cache.setNode(node1)
            result, x_i = coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, 3)
            dx_ds3 = [ (x_o[i] - x_i[i]) for i in range(3) ]
            result = coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, dx_ds3)
            dx_ds3 = [ -v for v in dx_ds3 ]
            cache.setNode(node2)
            result = coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, dx_ds3)

        # create extra node(s) at top of septum
        n2 = elementsCountUpSeptum + 1
        node1 = nodes.findNodeByIdentifier(laNodeId[1][n2][0])
        cache.setNode(node1)
        result, v1 = coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, 3)
        result, d1 = coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, 3)
        d1 = [ -d for d in d1 ]
        node2 = nodes.findNodeByIdentifier(raNodeId[1][n2][0])
        cache.setNode(node2)
        result, v2 = coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, 3)
        result, d2 = coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, 3)
        xi = 0.5
        vc = interpolateCubicHermite(v1, d1, v2, d2, xi )
        dc = interpolateCubicHermiteDerivative(v1, d1, v2, d2, xi )
        x = [ vc[0], vc[1], vc[2] ]
        # get magnitude of dx_ds1 from arc around apex to next node
        node = nodes.findNodeByIdentifier(apexNodeId[0])
        cache.setNode(node)
        result, ac = coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, 3)
        node = nodes.findNodeByIdentifier(laNodeId[1][n2 - 1][1])
        cache.setNode(node)
        result, vb = coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, 3)
        a = [ (vc[i] - ac[i]) for i in range(3) ]
        b = [ (vb[i] - ac[i]) for i in range(3) ]
        mag_a = math.sqrt(a[0]*a[0] + a[1]*a[1] + a[1]*a[1])
        mag_b = math.sqrt(b[0]*b[0] + b[1]*b[1] + b[1]*b[1])
        arcRadians = math.acos((a[0]*b[0] + a[1]*b[1] + a[1]*b[1]) / (mag_a*mag_b))
        mag = 0.5*(mag_a + mag_b)*arcRadians
        dx_ds1 = [ mag, 0.0, 0.0 ]
        dx_ds2 = [ 0.5*d for d in dc ]
        dx_ds3 = [ 0.0, 0.0, vc[2] + innerScaleZ*math.cos(math.pi - totalArcUpRadians + septumArcUpRadians) ]
        node = nodes.createNode(nodeIdentifier, nodetemplate)
        septumNodeId = nodeIdentifier
        cache.setNode(node)
        result = coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, x)
        #print('septum', node.isValid(), result, ' nodes', laNodeId[1][n2][0], raNodeId[1][n2][0])
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, dx_ds1)
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, dx_ds2)
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, dx_ds3)
        nodeIdentifier += 1

        if False:
            # show centre/axes of atria
            node = nodes.createNode(nodeIdentifier, nodetemplate)
            cache.setNode(node)
            result = coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, [ laCentreX, laCentreY, innerScaleZ*math.cos(totalArcUpRadians) ])
            result = coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, [ laInnerMajorX, laInnerMajorY, 0.0 ])
            result = coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, [ laInnerMinorX, laInnerMinorY, 0.0 ])
            result = coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, [ 0.0, 0.0, lengthRatio - freeWallThickness ])
            nodeIdentifier += 1

            # show axes of right atrium
            node = nodes.createNode(nodeIdentifier, nodetemplate)
            cache.setNode(node)
            result = coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, [ raCentreX, raCentreY, innerScaleZ*math.cos(totalArcUpRadians) ])
            result = coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, [ raInnerMajorX, raInnerMajorY, 0.0 ])
            result = coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, [ raInnerMinorX, raInnerMinorY, 0.0 ])
            result = coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, [ 0.0, 0.0, lengthRatio - freeWallThickness ])
            nodeIdentifier += 1

        #################
        # Create elements
        #################

        elementIdentifier = 1

        mesh = fm.findMeshByDimension(3)

        tricubichermite = eftfactory_tricubichermite(mesh, useCrossDerivatives)
        tricubicHermiteBasis = fm.createElementbasis(3, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE)

        eft = tricubichermite.createEftBasic()
        elementtemplate = mesh.createElementtemplate()
        elementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        elementtemplate.defineField(coordinates, -1, eft)

        eftSeptumSplitRight = tricubichermite.createEftNoCrossDerivatives()
        setEftScaleFactorIds(eftSeptumSplitRight, [1], [])
        remapEftNodeValueLabel(eftSeptumSplitRight, [ 1, 3 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS3, []) ])
        remapEftNodeValueLabel(eftSeptumSplitRight, [ 5, 7 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS1, [1]), (Node.VALUE_LABEL_D_DS3, []) ])
        #scaleEftNodeValueLabels(eftSeptumSplitRight, [ 5, 6, 7, 8 ], [ Node.VALUE_LABEL_D_DS3 ], [ 1 ])
        elementtemplateSeptumSplitRight = mesh.createElementtemplate()
        elementtemplateSeptumSplitRight.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        elementtemplateSeptumSplitRight.defineField(coordinates, -1, eftSeptumSplitRight)

        eftSeptumSplitCentre = tricubichermite.createEftNoCrossDerivatives()
        setEftScaleFactorIds(eftSeptumSplitCentre, [1], [])
        remapEftNodeValueLabel(eftSeptumSplitCentre, [ 5, 7 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS3, [1]) ])
        remapEftNodeValueLabel(eftSeptumSplitCentre, [ 6, 8 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, [1]), (Node.VALUE_LABEL_D_DS3, [1]) ])
        remapEftNodeValueLabel(eftSeptumSplitCentre, [ 6, 8 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS3, [1]) ])
        elementtemplateSeptumSplitCentre = mesh.createElementtemplate()
        elementtemplateSeptumSplitCentre.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        elementtemplateSeptumSplitCentre.defineField(coordinates, -1, eftSeptumSplitCentre)

        # regular rows within septum
        for e2 in range(elementsCountUpSeptum):

            for i in range(2):
                if i == 0:  # left
                    nodeId = laNodeId
                    otherNodeId = raNodeId
                else:  # right
                    nodeId = raNodeId
                    otherNodeId = laNodeId

                for e1 in range(elementsCountAround):

                    en = (e1 + 1) % elementsCountAround
                    nids = [ nodeId[0][e2][e1], nodeId[0][e2][en], nodeId[0][e2 + 1][e1], nodeId[0][e2 + 1][en], \
                             nodeId[1][e2][e1], nodeId[1][e2][en], nodeId[1][e2 + 1][e1], nodeId[1][e2 + 1][en] ]

                    if e1 == 0:
                        eft1 = eftSeptumSplitRight
                        elementtemplate1 = elementtemplateSeptumSplitRight
                        nids[4] = otherNodeId[1][e2][-1]
                        nids[6] = otherNodeId[1][e2 + 1][-1]
                    elif e1 == (elementsCountAround - 1):
                        eft1 = eftSeptumSplitCentre
                        elementtemplate1 = elementtemplateSeptumSplitCentre
                    else:
                        eft1 = eft
                        elementtemplate1 = elementtemplate

                    element = mesh.createElement(elementIdentifier, elementtemplate1)
                    result2 = element.setNodesByIdentifier(eft1, nids)
                    if eft1.getNumberOfLocalScaleFactors() == 1:
                        result3 = element.setScaleFactors(eft1, [ -1.0 ])
                    else:
                        result3 = 1
                    #print('create element', 'la' if i == 0 else 'ra', element.isValid(), elementIdentifier, result2, result3, nids)
                    elementIdentifier += 1

        # septum transition interior collapsed elements

        n2 = elementsCountUpSeptum
        nids = [
            [ laNodeId[0][n2][ 0], laNodeId[0][n2][1], raNodeId[1][n2][-1], laNodeId[1][n2][1], septumNodeId ],
            [ laNodeId[0][n2][-1], laNodeId[0][n2][0], laNodeId[1][n2][-1], laNodeId[1][n2][0], septumNodeId ],
            [ raNodeId[0][n2][ 0], raNodeId[0][n2][1], laNodeId[1][n2][-1], raNodeId[1][n2][1], septumNodeId ],
            [ raNodeId[0][n2][-1], raNodeId[0][n2][0], raNodeId[1][n2][-1], raNodeId[1][n2][0], septumNodeId ]
        ]

        for e in range(len(nids)):
            eft1 = tricubichermite.createEftNoCrossDerivatives()
            setEftScaleFactorIds(eft1, [1], [])
            if e in [ 0, 2 ]:
                remapEftNodeValueLabel(eft1, [ 1, 2, 3, 4, 6, 8 ], Node.VALUE_LABEL_D_DS2, [ ] )
                remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS3, []) ])
                remapEftNodeValueLabel(eft1, [ 3 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS2, []), (Node.VALUE_LABEL_D_DS3, []) ])
                remapEftNodeValueLabel(eft1, [ 3 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS2, []), (Node.VALUE_LABEL_D_DS3, []) ])
                remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS2, []) ])
                remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS1, [1]), (Node.VALUE_LABEL_D_DS3, []) ])
                if e == 0:
                    #remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, [1]) ])
                    remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, [1]), (Node.VALUE_LABEL_D_DS2, [1]) ])
                    #remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS1, []) ])
                    remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS2, [1]) ])
                    remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS2, []), (Node.VALUE_LABEL_D_DS3, []) ])
                else:
                    remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS2, []) ])
                    #remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS1, [1]) ])
                    remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS1, [1]), (Node.VALUE_LABEL_D_DS2, []) ])
                    remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS2, [1]), (Node.VALUE_LABEL_D_DS3, []) ])
                remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS2, [1]) ])
                ln_map = [ 1, 2, 1, 2, 3, 4, 5, 4 ]
                remapEftLocalNodes(eft1, 5, ln_map)
            else:
                remapEftNodeValueLabel(eft1, [ 1, 2, 3, 4, 5, 7 ], Node.VALUE_LABEL_D_DS2, [ ] )
                remapEftNodeValueLabel(eft1, [ 4 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS2, []), (Node.VALUE_LABEL_D_DS3, []) ])
                remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS3, [1]) ])
                remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, [1]), (Node.VALUE_LABEL_D_DS3, [1]) ])
                remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS2, []), (Node.VALUE_LABEL_D_DS3, []) ])
                remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS3, [1]) ])
                remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS2, []) ])
                if e == 1:
                    #remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, [1]) ])
                    remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, [1]), (Node.VALUE_LABEL_D_DS2, []) ])
                    remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS2, [1]), (Node.VALUE_LABEL_D_DS3, []) ])
                    remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS2, []), (Node.VALUE_LABEL_D_DS3, []) ])
                else:
                    remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS2, [1]) ])
                    remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS2, []), (Node.VALUE_LABEL_D_DS3, []) ])
                    remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS2, [1]), (Node.VALUE_LABEL_D_DS3, []) ])
                ln_map = [ 1, 2, 1, 2, 3, 4, 3, 5 ]
                remapEftLocalNodes(eft1, 5, ln_map)

            elementtemplate1 = mesh.createElementtemplate()
            elementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE)
            result = elementtemplate1.defineField(coordinates, -1, eft1)

            element = mesh.createElement(elementIdentifier, elementtemplate1)
            result2 = element.setNodesByIdentifier(eft1, nids[e])
            if eft1.getNumberOfLocalScaleFactors() == 1:
                result3 = element.setScaleFactors(eft1, [ -1.0 ])
            else:
                result3 = 1
            #print('create element st', elementIdentifier, result, result2, result3, nids[e])
            elementIdentifier += 1

        # semi-regular rows above septum but below apex, including second septum transition elements

        for e2 in range(elementsCountUpSeptum, elementsCountUp - 1):

            for i in range(2):
                if i == 0:  # left
                    nodeId = laNodeId
                    otherNodeId = raNodeId
                else:  # right
                    nodeId = raNodeId
                    otherNodeId = laNodeId

                for e1 in range(elementsCountAround):

                    en = (e1 + 1) % elementsCountAround
                    nids = [ nodeId[0][e2][e1], nodeId[0][e2][en], nodeId[0][e2 + 1][e1], nodeId[0][e2 + 1][en], \
                             nodeId[1][e2][e1], nodeId[1][e2][en], nodeId[1][e2 + 1][e1], nodeId[1][e2 + 1][en] ]

                    if (e2 == elementsCountUpSeptum) and ((e1 == 0) or (e1 == elementsCountAround - 1)):
                        eft1 = tricubichermite.createEftNoCrossDerivatives()
                        setEftScaleFactorIds(eft1, [1], [])
                        if e1 == 0:
                            nids[4] = septumNodeId
                            remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS2, []), (Node.VALUE_LABEL_D_DS3, []) ])
                            remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, [1 if (i == 0) else 0]), (Node.VALUE_LABEL_D_DS2, [1 if (i == 0) else 0]) ])
                            remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS2, [1 if (i == 0) else 0]) ])
                            remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS2, [1 if (i == 0) else 0]), (Node.VALUE_LABEL_D_DS3, [0]) ])
                            remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS2, [1]) ])
                        else:
                            nids[5] = septumNodeId
                            remapEftNodeValueLabel(eft1, [ 2 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS2, []), (Node.VALUE_LABEL_D_DS3, []) ])
                            remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS2, []) ])
                            remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, [1 if (i == 0) else 0]), (Node.VALUE_LABEL_D_DS2, [0 if (i == 0) else 1]) ])
                            remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS2, [1 if (i == 0) else 0]) ])
                            remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS3, [ (Node.VALUE_LABEL_D_DS2, [1 if (i == 0) else 0]), (Node.VALUE_LABEL_D_DS3, [0]) ])
                        elementtemplate1 = mesh.createElementtemplate()
                        elementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE)
                        result = elementtemplate1.defineField(coordinates, -1, eft1)
                    else:
                        eft1 = eft
                        elementtemplate1 = elementtemplate

                    element = mesh.createElement(elementIdentifier, elementtemplate1)
                    result2 = element.setNodesByIdentifier(eft1, nids)
                    if eft1.getNumberOfLocalScaleFactors() == 1:
                        result3 = element.setScaleFactors(eft1, [ -1.0 ])
                    else:
                        result3 = 1
                    #print('create element', 'la' if i == 0 else 'ra', element.isValid(), elementIdentifier, result2, result3, nids)
                    if e2 == elementsCountUp - 2:
                        if i == 0:
                            if e1 == 0:
                                rapvElementId = elementIdentifier
                            elif e1 == (elementsCountAround - 1):
                                rppvElementId = elementIdentifier
                            elif e1 == (elementsCountAround//2 - 1):
                                lapvElementId = elementIdentifier
                            elif e1 == ((elementsCountAround + 1)//2):
                                lppvElementId = elementIdentifier
                        else:  # i == 1:
                            if e1 == 1:
                                ivcElementId = elementIdentifier
                            elif e1 == (elementsCountAround - 2):
                                svcElementId = elementIdentifier

                    elementIdentifier += 1

        for i in range(2):
            nodeId = laNodeId if (i == 0) else raNodeId
            apexNodeId = laApexNodeId if (i == 0) else raApexNodeId
            aRadians = laRadians if (i == 0) else raRadians
            deltaRadians = laDeltaRadians if (i == 0) else raDeltaRadians

            # create top apex elements
            # scale factor identifiers follow convention of offsetting by 100 for each 'version'
            elementtemplate1 = mesh.createElementtemplate()
            elementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE)
            for e1 in range(elementsCountAround):
                va = e1
                vb = (e1 + 1)%elementsCountAround
                eft1 = tricubichermite.createEftShellApexTop(va*100, vb*100)
                elementtemplate1.defineField(coordinates, -1, eft1)
                element = mesh.createElement(elementIdentifier, elementtemplate1)
                n2 = elementsCountUp - 1
                nodeIdentifiers = [ nodeId[0][n2][va], nodeId[0][n2][vb], apexNodeId[0], nodeId[1][n2][va], nodeId[1][n2][vb], apexNodeId[1] ]
                element.setNodesByIdentifier(eft1, nodeIdentifiers)
                # set general linear map coefficients
                scalefactors = [
                    -1.0,
                    -math.cos(aRadians[va]), -math.sin(aRadians[va]), deltaRadians[va],
                    -math.cos(aRadians[vb]), -math.sin(aRadians[vb]), deltaRadians[vb],
                    -math.cos(aRadians[va]), -math.sin(aRadians[va]), deltaRadians[va],
                    -math.cos(aRadians[vb]), -math.sin(aRadians[vb]), deltaRadians[vb]
                ]
                result = element.setScaleFactors(eft1, scalefactors)
                elementIdentifier = elementIdentifier + 1

        # add right atria inlets (venae cavae)

        for elementId in [ ivcElementId, svcElementId ]:
            element = mesh.findElementByIdentifier(elementId)
            vcLength = vcInnerDiameter*0.5
            tricubichermite.replaceElementWithInlet4(element, elementIdentifier, nodetemplate, nodeIdentifier, vcLength, vcInnerDiameter, vcWallThickness)
            elementIdentifier += 4
            nodeIdentifier += 8
            mesh.destroyElement(element)

		# add left atria inlets (pulmonary veins)

        for elementId in [ lapvElementId, lppvElementId, rapvElementId, rppvElementId ]:
            element = mesh.findElementByIdentifier(elementId)
            pvLength = pvInnerDiameter*0.5
            tricubichermite.replaceElementWithInlet4(element, elementIdentifier, nodetemplate, nodeIdentifier, pvLength, pvInnerDiameter, pvWallThickness)
            elementIdentifier += 4
            nodeIdentifier += 8
            mesh.destroyElement(element)

        fm.endChange()
Beispiel #10
0
    def generateElements(self,
                         fieldmodule,
                         coordinates,
                         startElementIdentifier,
                         meshGroups=[]):
        """
        Create shield elements from nodes.
        :param fieldmodule: Zinc fieldmodule to create elements in.
        :param coordinates: Coordinate field to define.
        :param startElementIdentifier: First element identifier to use.
        :param meshGroups: Zinc mesh groups to add elements to.
        :return: next elementIdentifier.
         """
        elementIdentifier = startElementIdentifier
        useCrossDerivatives = False
        mesh = fieldmodule.findMeshByDimension(3)

        tricubichermite = eftfactory_tricubichermite(mesh, useCrossDerivatives)
        eft = tricubichermite.createEftNoCrossDerivatives()
        elementtemplate = mesh.createElementtemplate()
        elementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        elementtemplate.defineField(coordinates, -1, eft)

        elementtemplate1 = mesh.createElementtemplate()
        elementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE)

        isEven = (self.elementsCountAcross % 2) == 0
        e1a = self.elementsCountRim
        e1b = e1a + 1
        e1z = self.elementsCountAcross - 1 - self.elementsCountRim
        e1y = e1z - 1
        e2a = self.elementsCountRim
        e2b = self.elementsCountRim + 1
        e2c = self.elementsCountRim + 2
        e2d = 2 * self.elementsCountUp - 1

        for e3 in range(self.elementsCountAlong):
            for e2 in range(self.elementsCountUpFull):
                for e1 in range(self.elementsCountAcross):
                    eft1 = eft
                    scalefactors = None
                    if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
                        nids = [
                            self.nodeId[e3][e2][e1],
                            self.nodeId[e3][e2 + 1][e1],
                            self.nodeId[e3 + 1][e2][e1],
                            self.nodeId[e3 + 1][e2 + 1][e1],
                            self.nodeId[e3][e2][e1 + 1],
                            self.nodeId[e3][e2 + 1][e1 + 1],
                            self.nodeId[e3 + 1][e2][e1 + 1],
                            self.nodeId[e3 + 1][e2 + 1][e1 + 1]
                        ]
                    elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
                        nids = [
                            self.nodeId[0][e2][e1], self.nodeId[0][e2][e1 + 1],
                            self.nodeId[0][e2 + 1][e1],
                            self.nodeId[0][e2 + 1][e1 + 1],
                            self.nodeId[1][e2][e1], self.nodeId[1][e2][e1 + 1],
                            self.nodeId[1][e2 + 1][e1],
                            self.nodeId[1][e2 + 1][e1 + 1]
                        ]
                    if (e2 < e2b) or (e2 == e2d):
                        if (e1 < e1b) or (e1 > e1y):
                            continue  # no element due to triple point closure
                        if (e2 == e2a) or (e2 == e2d):
                            # bottom and top row elements
                            if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
                                if e2 == e2a:
                                    eft1 = tricubichermite.createEftNoCrossDerivatives(
                                    )
                                    setEftScaleFactorIds(eft1, [1], [])
                                    scalefactors = [-1.0]
                                    remapEftNodeValueLabel(
                                        eft1, [1, 3, 5, 7],
                                        Node.VALUE_LABEL_D_DS1,
                                        [(Node.VALUE_LABEL_D_DS3, [1])])
                                    remapEftNodeValueLabel(
                                        eft1, [1, 3, 5, 7],
                                        Node.VALUE_LABEL_D_DS3,
                                        [(Node.VALUE_LABEL_D_DS1, [])])
                                    if (e1 == e1b) or (e1 == e1y):
                                        # map bottom triple point element
                                        if e1 == e1b:
                                            remapEftNodeValueLabel(
                                                eft1, [2, 4],
                                                Node.VALUE_LABEL_D_DS1,
                                                [(Node.VALUE_LABEL_D_DS1, []),
                                                 (Node.VALUE_LABEL_D_DS3, [])])
                                        else:
                                            remapEftNodeValueLabel(
                                                eft1, [6, 8],
                                                Node.VALUE_LABEL_D_DS1,
                                                [(Node.VALUE_LABEL_D_DS1, []),
                                                 (Node.VALUE_LABEL_D_DS3, [1])
                                                 ])
                                elif e2 == e2d:
                                    eft1 = tricubichermite.createEftNoCrossDerivatives(
                                    )
                                    setEftScaleFactorIds(eft1, [1], [])
                                    scalefactors = [-1.0]
                                    remapEftNodeValueLabel(
                                        eft1, [2, 4, 6, 8],
                                        Node.VALUE_LABEL_D_DS3,
                                        [(Node.VALUE_LABEL_D_DS1, [1])])
                                    remapEftNodeValueLabel(
                                        eft1, [2, 4, 6, 8],
                                        Node.VALUE_LABEL_D_DS1,
                                        [(Node.VALUE_LABEL_D_DS3, [])])
                                    if (e1 == e1b) or (e1 == e1y):
                                        # map top triple point element
                                        if e1 == e1b:
                                            remapEftNodeValueLabel(
                                                eft1, [1, 3],
                                                Node.VALUE_LABEL_D_DS1,
                                                [(Node.VALUE_LABEL_D_DS1, []),
                                                 (Node.VALUE_LABEL_D_DS3, [1])
                                                 ])
                                        else:
                                            remapEftNodeValueLabel(
                                                eft1, [5, 7],
                                                Node.VALUE_LABEL_D_DS1,
                                                [(Node.VALUE_LABEL_D_DS1, []),
                                                 (Node.VALUE_LABEL_D_DS3, [])])
                            elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
                                if (e1 == e1b) or (e1 == e1y):
                                    # map bottom triple point element
                                    eft1 = tricubichermite.createEftNoCrossDerivatives(
                                    )
                                    setEftScaleFactorIds(eft1, [1], [])
                                    scalefactors = [-1.0]
                                    if e1 == e1b:
                                        remapEftNodeValueLabel(
                                            eft1, [3, 7],
                                            Node.VALUE_LABEL_D_DS2,
                                            [(Node.VALUE_LABEL_D_DS1, []),
                                             (Node.VALUE_LABEL_D_DS2, [])])
                                    else:
                                        remapEftNodeValueLabel(
                                            eft1, [4, 8],
                                            Node.VALUE_LABEL_D_DS2,
                                            [(Node.VALUE_LABEL_D_DS1, [1]),
                                             (Node.VALUE_LABEL_D_DS2, [])])

                    elif (e2 == e2b) or (e2 == e2d - e2b):
                        if (e1 <= e1a) or (e1 >= e1z):
                            # map top 2 triple point elements
                            eft1 = tricubichermite.createEftNoCrossDerivatives(
                            )
                            setEftScaleFactorIds(eft1, [1], [])
                            scalefactors = [-1.0]
                            if e1 < e1a:
                                e2r = e1
                                nids[0] = self.nodeId[0][e2r][e1b]
                                nids[1] = self.nodeId[0][e2r + 1][e1b]
                                nids[4] = self.nodeId[1][e2r][e1b]
                                nids[5] = self.nodeId[1][e2r + 1][e1b]
                                remapEftNodeValueLabel(
                                    eft1, [1, 2, 5, 6], Node.VALUE_LABEL_D_DS2,
                                    [(Node.VALUE_LABEL_D_DS1, [1])])
                                remapEftNodeValueLabel(
                                    eft1, [1, 2, 5, 6], Node.VALUE_LABEL_D_DS1,
                                    [(Node.VALUE_LABEL_D_DS2, [])])
                            elif e1 == e1a:
                                if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
                                    if e2 == e2b:
                                        nids[0] = self.nodeId[e3][e2a][e1b]
                                        nids[2] = self.nodeId[e3 + 1][e2a][e1b]
                                        tripleN = [5, 7]
                                        remapEftNodeValueLabel(
                                            eft1, tripleN,
                                            Node.VALUE_LABEL_D_DS3,
                                            [(Node.VALUE_LABEL_D_DS1, []),
                                             (Node.VALUE_LABEL_D_DS3, [])])
                                    elif e2 == e2d - e2b:
                                        nids[1] = self.nodeId[e3][e2d + 1][e1b]
                                        nids[3] = self.nodeId[e3 + 1][e2d +
                                                                      1][e1b]
                                        tripleN = [6, 8]
                                        remapEftNodeValueLabel(
                                            eft1, tripleN,
                                            Node.VALUE_LABEL_D_DS3,
                                            [(Node.VALUE_LABEL_D_DS1, [1]),
                                             (Node.VALUE_LABEL_D_DS3, [])])
                                    remapEftNodeValueLabel(
                                        eft1, [1, 2, 3, 4],
                                        Node.VALUE_LABEL_D_DS1,
                                        [(Node.VALUE_LABEL_D_DS1, [1])])
                                    remapEftNodeValueLabel(
                                        eft1, [1, 2, 3, 4],
                                        Node.VALUE_LABEL_D_DS3,
                                        [(Node.VALUE_LABEL_D_DS3, [1])])
                                elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
                                    nids[0] = self.nodeId[0][e2a][e1b]
                                    nids[4] = self.nodeId[1][e2a][e1b]
                                    remapEftNodeValueLabel(
                                        eft1, [1, 5], Node.VALUE_LABEL_D_DS2,
                                        [(Node.VALUE_LABEL_D_DS1, [1])])
                                    remapEftNodeValueLabel(
                                        eft1, [1, 5], Node.VALUE_LABEL_D_DS1,
                                        [(Node.VALUE_LABEL_D_DS2, [])])
                                    remapEftNodeValueLabel(
                                        eft1, [2, 6], Node.VALUE_LABEL_D_DS1,
                                        [(Node.VALUE_LABEL_D_DS1, []),
                                         (Node.VALUE_LABEL_D_DS2, [])])
                            elif e1 == e1z:
                                if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
                                    if e2 == e2b:
                                        nids[4] = self.nodeId[e3][e2a][e1z]
                                        nids[6] = self.nodeId[e3 + 1][e2a][e1z]
                                        remapEftNodeValueLabel(
                                            eft1, [1, 3],
                                            Node.VALUE_LABEL_D_DS3,
                                            [(Node.VALUE_LABEL_D_DS1, [1]),
                                             (Node.VALUE_LABEL_D_DS3, [])])
                                    elif e2 == e2d - e2b:
                                        nids[5] = self.nodeId[e3][e2d + 1][e1z]
                                        nids[7] = self.nodeId[e3 + 1][e2d +
                                                                      1][e1z]
                                        remapEftNodeValueLabel(
                                            eft1, [2, 4],
                                            Node.VALUE_LABEL_D_DS3,
                                            [(Node.VALUE_LABEL_D_DS1, []),
                                             (Node.VALUE_LABEL_D_DS3, [])])
                                elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
                                    nids[1] = self.nodeId[0][e2a][e1z]
                                    nids[5] = self.nodeId[1][e2a][e1z]
                                    remapEftNodeValueLabel(
                                        eft1, [1, 5], Node.VALUE_LABEL_D_DS1,
                                        [(Node.VALUE_LABEL_D_DS1, []),
                                         (Node.VALUE_LABEL_D_DS2, [1])])
                                    remapEftNodeValueLabel(
                                        eft1, [2, 6], Node.VALUE_LABEL_D_DS1,
                                        [(Node.VALUE_LABEL_D_DS2, [1])])
                                    remapEftNodeValueLabel(
                                        eft1, [2, 6], Node.VALUE_LABEL_D_DS2,
                                        [(Node.VALUE_LABEL_D_DS1, [])])
                            elif e1 > e1z:
                                e2r = self.elementsCountAcross - e1
                                nids[0] = self.nodeId[0][e2r][e1z]
                                nids[1] = self.nodeId[0][e2r - 1][e1z]
                                nids[4] = self.nodeId[1][e2r][e1z]
                                nids[5] = self.nodeId[1][e2r - 1][e1z]
                                remapEftNodeValueLabel(
                                    eft1, [1, 2, 5, 6], Node.VALUE_LABEL_D_DS1,
                                    [(Node.VALUE_LABEL_D_DS2, [1])])
                                remapEftNodeValueLabel(
                                    eft1, [1, 2, 5, 6], Node.VALUE_LABEL_D_DS2,
                                    [(Node.VALUE_LABEL_D_DS1, [])])
                    else:
                        if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
                            if (e1 <= e1a):
                                # map left column elements
                                eft1 = tricubichermite.createEftNoCrossDerivatives(
                                )
                                setEftScaleFactorIds(eft1, [1], [])
                                scalefactors = [-1.0]
                                remapEftNodeValueLabel(
                                    eft1, [1, 2, 3, 4], Node.VALUE_LABEL_D_DS1,
                                    [(Node.VALUE_LABEL_D_DS1, [1])])
                                remapEftNodeValueLabel(
                                    eft1, [1, 2, 3, 4], Node.VALUE_LABEL_D_DS3,
                                    [(Node.VALUE_LABEL_D_DS3, [1])])

                    if eft1 is not eft:
                        elementtemplate1.defineField(coordinates, -1, eft1)
                        element = mesh.createElement(elementIdentifier,
                                                     elementtemplate1)
                    else:
                        element = mesh.createElement(elementIdentifier,
                                                     elementtemplate)
                    result2 = element.setNodesByIdentifier(eft1, nids)
                    if scalefactors:
                        result3 = element.setScaleFactors(eft1, scalefactors)
                    else:
                        result3 = 7
                    #print('create element shield', elementIdentifier, result2, result3, nids)
                    self.elementId[e2][e1] = elementIdentifier
                    elementIdentifier += 1

                    for meshGroup in meshGroups:
                        meshGroup.addElement(element)

        return elementIdentifier
Beispiel #11
0
    def generateBaseMesh(cls, region, options):
        """
        Generate the base tricubic Hermite mesh.
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: [] empty list of AnnotationGroup
        """
        elementsCountAround = options['Number of elements around']
        elementsCountUp = options['Number of elements up']
        elementsCountRadial = options['Number of elements radial']
        useCrossDerivatives = options['Use cross derivatives']
        radius = 0.5 * options['Diameter']

        fm = region.getFieldmodule()
        fm.beginChange()
        coordinates = findOrCreateFieldCoordinates(fm)

        nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
        nodetemplateApex = nodes.createNodetemplate()
        nodetemplateApex.defineField(coordinates)
        nodetemplateApex.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_VALUE, 1)
        nodetemplateApex.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1)
        nodetemplateApex.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1)
        nodetemplateApex.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D_DS3, 1)
        if useCrossDerivatives:
            nodetemplate = nodes.createNodetemplate()
            nodetemplate.defineField(coordinates)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_VALUE, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS1DS2,
                                                  1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D_DS3, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS1DS3,
                                                  1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS2DS3,
                                                  1)
            nodetemplate.setValueNumberOfVersions(
                coordinates, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1)
        else:
            nodetemplate = nodetemplateApex

        cache = fm.createFieldcache()

        #################
        # Create nodes
        #################

        nodeIdentifier = 1
        radiansPerElementAround = 2.0 * math.pi / elementsCountAround
        radiansPerElementUp = math.pi / elementsCountUp

        x = [0.0, 0.0, 0.0]
        dx_ds1 = [0.0, 0.0, 0.0]
        dx_ds2 = [0.0, 0.0, 0.0]
        dx_ds3 = [0.0, 0.0, 0.0]
        zero = [0.0, 0.0, 0.0]

        cubicArcLengthList = [0.0] * (elementsCountUp + 1)

        # Pre-calculate cubicArcLength along elementsCountUp
        for n2 in range(1, elementsCountUp + 1):
            radiansUp = n2 * radiansPerElementUp
            cosRadiansUp = math.cos(radiansUp)
            sinRadiansUp = math.sin(radiansUp)

            # Calculate cubic hermite arclength linking point on axis to surface on sphere
            v1 = [0.0, 0.0, -radius + n2 * 2.0 * radius / elementsCountUp]
            d1 = [0.0, 1.0, 0.0]
            v2 = [
                radius * math.cos(math.pi / 2.0) * sinRadiansUp,
                radius * math.sin(math.pi / 2.0) * sinRadiansUp,
                -radius * cosRadiansUp
            ]
            d2 = [
                math.cos(math.pi / 2.0) * sinRadiansUp,
                math.sin(math.pi / 2.0) * sinRadiansUp, -cosRadiansUp
            ]
            cubicArcLengthList[n2] = interp.computeCubicHermiteArcLength(
                v1, d1, v2, d2, True)

        # Create node for bottom pole
        node = nodes.createNode(nodeIdentifier, nodetemplate)
        cache.setNode(node)
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1,
                                      [0.0, 0.0, -radius])
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1,
                                      [radius * radiansPerElementUp, 0.0, 0.0])
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1,
                                      [0.0, radius * radiansPerElementUp, 0.0])
        coordinates.setNodeParameters(
            cache, -1, Node.VALUE_LABEL_D_DS3, 1,
            [0.0, 0.0, -radius * 2.0 / elementsCountUp])
        if useCrossDerivatives:
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D2_DS1DS2, 1, zero)
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D2_DS1DS3, 1, zero)
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D2_DS2DS3, 1, zero)
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D3_DS1DS2DS3, 1,
                                          zero)
        nodeIdentifier = nodeIdentifier + 1

        # Create nodes along axis between top and bottom poles
        for n2 in range(1, elementsCountUp):
            node = nodes.createNode(nodeIdentifier, nodetemplate)
            cache.setNode(node)
            coordinates.setNodeParameters(
                cache, -1, Node.VALUE_LABEL_VALUE, 1,
                [0.0, 0.0, -radius + n2 * 2.0 * radius / elementsCountUp])
            coordinates.setNodeParameters(
                cache, -1, Node.VALUE_LABEL_D_DS1, 1,
                [cubicArcLengthList[n2] / elementsCountRadial, 0.0, 0.0])
            coordinates.setNodeParameters(
                cache, -1, Node.VALUE_LABEL_D_DS2, 1,
                [0.0, 0.0, radius * 2.0 / elementsCountUp])
            coordinates.setNodeParameters(
                cache, -1, Node.VALUE_LABEL_D_DS3, 1,
                [0.0, cubicArcLengthList[n2] / elementsCountRadial, 0.0])
            if useCrossDerivatives:
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D2_DS1DS2, 1,
                                              zero)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D2_DS1DS3, 1,
                                              zero)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D2_DS2DS3, 1,
                                              zero)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D3_DS1DS2DS3, 1,
                                              zero)
            nodeIdentifier = nodeIdentifier + 1

        # Create nodes for top pole
        node = nodes.createNode(nodeIdentifier, nodetemplate)
        cache.setNode(node)
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1,
                                      [0.0, 0.0, radius])
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1,
                                      [radius * radiansPerElementUp, 0.0, 0.0])
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1,
                                      [0.0, radius * radiansPerElementUp, 0.0])
        coordinates.setNodeParameters(
            cache, -1, Node.VALUE_LABEL_D_DS3, 1,
            [0.0, 0.0, radius * 2.0 / elementsCountUp])
        if useCrossDerivatives:
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D2_DS1DS2, 1, zero)
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D2_DS1DS3, 1, zero)
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D2_DS2DS3, 1, zero)
            coordinates.setNodeParameters(cache, -1,
                                          Node.VALUE_LABEL_D3_DS1DS2DS3, 1,
                                          zero)
        nodeIdentifier = nodeIdentifier + 1

        # Create other nodes
        for n3 in range(1, elementsCountRadial + 1):
            xi = 1 / elementsCountRadial * n3
            radiansUpArcOriginList = [0.0] * (elementsCountUp)

            # Pre-calculate RC for points on vertical arc running between top and bottom poles
            pt = [0.0, radius * xi, 0.0]
            arcOrigin = (radius * radius - pt[2] * pt[2] -
                         pt[1] * pt[1]) / (-2.0 * pt[1])
            RC = math.sqrt(arcOrigin * arcOrigin + radius * radius)

            radiansUpArcOriginList[0] = math.acos(-radius / RC)

            # Identify nodes on the vertical arc using radiansAround = pi/2
            for n2 in range(1, elementsCountUp):
                radiansUp = n2 * radiansPerElementUp
                cosRadiansUp = math.cos(radiansUp)
                sinRadiansUp = math.sin(radiansUp)

                # Calculate node coordinates on arc using cubic hermite interpolation
                cubicArcLength = cubicArcLengthList[n2]
                v1 = [0.0, 0.0, -radius + n2 * 2.0 * radius / elementsCountUp]
                d1 = [math.cos(math.pi / 2.0), math.sin(math.pi / 2.0), 0.0]
                d1 = vector.normalise(d1)
                d1 = [d * cubicArcLength for d in d1]
                v2 = [
                    radius * math.cos(math.pi / 2.0) * sinRadiansUp,
                    radius * math.sin(math.pi / 2.0) * sinRadiansUp,
                    -radius * cosRadiansUp
                ]
                d2 = [
                    math.cos(math.pi / 2.0) * sinRadiansUp,
                    math.sin(math.pi / 2.0) * sinRadiansUp, -cosRadiansUp
                ]
                d2 = vector.normalise(d2)
                d2 = [d * cubicArcLength for d in d2]
                x = interp.interpolateCubicHermite(v1, d1, v2, d2, xi)

                # Calculate radiansUp for each point wrt arcOrigin
                radiansUpArcOriginList[n2] = math.acos(x[2] / RC)

            for n2 in range(1, elementsCountUp):
                radiansUp = n2 * radiansPerElementUp
                cosRadiansUp = math.cos(radiansUp)
                sinRadiansUp = math.sin(radiansUp)

                for n1 in range(elementsCountAround):
                    radiansAround = n1 * radiansPerElementAround
                    cosRadiansAround = math.cos(radiansAround)
                    sinRadiansAround = math.sin(radiansAround)
                    cubicArcLength = cubicArcLengthList[n2]

                    # Calculate node coordinates on arc using cubic hermite interpolation
                    v1 = [
                        0.0, 0.0, -radius + n2 * 2.0 * radius / elementsCountUp
                    ]
                    d1 = [cosRadiansAround, sinRadiansAround, 0.0]
                    d1 = vector.normalise(d1)
                    d1 = [d * cubicArcLength for d in d1]
                    v2 = [
                        radius * cosRadiansAround * sinRadiansUp,
                        radius * sinRadiansAround * sinRadiansUp,
                        -radius * cosRadiansUp
                    ]
                    d2 = [
                        cosRadiansAround * sinRadiansUp,
                        sinRadiansAround * sinRadiansUp, -cosRadiansUp
                    ]
                    d2 = vector.normalise(d2)
                    d2 = [d * cubicArcLength for d in d2]
                    x = interp.interpolateCubicHermite(v1, d1, v2, d2, xi)

                    # For dx_ds1 - Calculate radius wrt origin where interpolated points lie on
                    orthoRadius = vector.magnitude(x)
                    orthoRadiansUp = math.pi - math.acos(x[2] / orthoRadius)
                    sinOrthoRadiansUp = math.sin(orthoRadiansUp)
                    cosOrthoRadiansUp = math.cos(orthoRadiansUp)

                    # For dx_ds2 - Assign radiansUp from radiansUpArcOriginList and calculate diff between radiansUp as we move up
                    radiansUpArcOrigin = radiansUpArcOriginList[n2]
                    sinRadiansUpArcOrigin = math.sin(radiansUpArcOrigin)
                    cosRadiansUpArcOrigin = math.cos(radiansUpArcOrigin)
                    radiansPerElementUpArcOrigin = radiansUpArcOriginList[
                        n2] - radiansUpArcOriginList[n2 - 1]

                    dx_ds1 = [
                        orthoRadius * -sinRadiansAround * sinOrthoRadiansUp *
                        radiansPerElementAround,
                        orthoRadius * cosRadiansAround * sinOrthoRadiansUp *
                        radiansPerElementAround, 0.0
                    ]

                    dx_ds2 = [
                        RC * cosRadiansAround * cosRadiansUpArcOrigin *
                        radiansPerElementUpArcOrigin, RC * sinRadiansAround *
                        cosRadiansUpArcOrigin * radiansPerElementUpArcOrigin,
                        -RC * sinRadiansUpArcOrigin *
                        radiansPerElementUpArcOrigin
                    ]

                    dx_ds3 = interp.interpolateCubicHermiteDerivative(
                        v1, d1, v2, d2, xi)
                    dx_ds3 = vector.normalise(dx_ds3)
                    dx_ds3 = [
                        d * cubicArcLength / elementsCountRadial
                        for d in dx_ds3
                    ]

                    node = nodes.createNode(nodeIdentifier, nodetemplate)
                    cache.setNode(node)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_VALUE, 1, x)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1,
                                                  dx_ds1)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1,
                                                  dx_ds2)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS3, 1,
                                                  dx_ds3)
                    if useCrossDerivatives:
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D2_DS1DS2, 1, zero)
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D2_DS1DS3, 1, zero)
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D2_DS2DS3, 1, zero)
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1, zero)
                    nodeIdentifier = nodeIdentifier + 1

        #################
        # Create elements
        #################

        mesh = fm.findMeshByDimension(3)

        tricubichermite = eftfactory_tricubichermite(mesh, useCrossDerivatives)
        eft = tricubichermite.createEftBasic()

        tricubicHermiteBasis = fm.createElementbasis(
            3, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE)

        # Regular elements
        elementtemplate = mesh.createElementtemplate()
        elementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        elementtemplate.defineField(coordinates, -1, eft)

        # Bottom tetrahedon elements
        elementtemplate1 = mesh.createElementtemplate()
        elementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE)

        # Axial elements
        elementtemplate2 = mesh.createElementtemplate()
        elementtemplate2.setElementShapeType(Element.SHAPE_TYPE_CUBE)

        # Top tetrahedron elements
        elementtemplate3 = mesh.createElementtemplate()
        elementtemplate3.setElementShapeType(Element.SHAPE_TYPE_CUBE)

        # Bottom pyramid elements
        elementtemplate4 = mesh.createElementtemplate()
        elementtemplate4.setElementShapeType(Element.SHAPE_TYPE_CUBE)

        # Top pyramid elements
        elementtemplate5 = mesh.createElementtemplate()
        elementtemplate5.setElementShapeType(Element.SHAPE_TYPE_CUBE)

        elementIdentifier = 1

        no2 = elementsCountAround
        no3 = elementsCountAround * (elementsCountUp - 1)
        rni = (1 + elementsCountUp) - no3 - no2 + 1  # regular node identifier
        radiansPerElementAround = 2.0 * math.pi / elementsCountAround

        for e3 in range(elementsCountRadial):

            # Create elements on bottom pole
            radiansIncline = math.pi * 0.5 * e3 / elementsCountRadial
            radiansInclineNext = math.pi * 0.5 * (e3 + 1) / elementsCountRadial

            if e3 == 0:
                # Create tetrahedron elements on the bottom pole
                bni1 = elementsCountUp + 2

                for e1 in range(elementsCountAround):
                    va = e1
                    vb = (e1 + 1) % elementsCountAround
                    eft1 = tricubichermite.createEftTetrahedronBottom(
                        va * 100, vb * 100, 10000)
                    elementtemplate1.defineField(coordinates, -1, eft1)
                    element = mesh.createElement(elementIdentifier,
                                                 elementtemplate1)
                    nodeIdentifiers = [1, 2, bni1 + va, bni1 + vb]
                    result1 = element.setNodesByIdentifier(
                        eft1, nodeIdentifiers)
                    # set general linear map coefficients
                    radiansAround = va * radiansPerElementAround
                    radiansAroundNext = vb * radiansPerElementAround
                    scalefactors = [
                        -1.0,
                        math.cos(radiansAround),
                        math.sin(radiansAround), radiansPerElementAround,
                        math.cos(radiansAroundNext),
                        math.sin(radiansAroundNext), radiansPerElementAround,
                        math.cos(radiansAround),
                        math.sin(radiansAround), radiansPerElementAround,
                        math.cos(radiansAroundNext),
                        math.sin(radiansAroundNext), radiansPerElementAround,
                        math.cos(radiansIncline),
                        math.sin(radiansIncline),
                        math.cos(radiansInclineNext),
                        math.sin(radiansInclineNext)
                    ]
                    result2 = element.setScaleFactors(eft1, scalefactors)
                    # print('Tetrahedron Bottom element', elementIdentifier, result1, result2, nodeIdentifiers)
                    elementIdentifier = elementIdentifier + 1

            else:
                # Create pyramid elements on the bottom pole
                bni4 = elementsCountUp + 1 + (e3 - 1) * no3 + 1

                for e1 in range(elementsCountAround):
                    va = e1
                    vb = (e1 + 1) % elementsCountAround
                    eft4 = tricubichermite.createEftPyramidBottom(
                        va * 100, vb * 100, 100000 + e3 * 2)
                    elementtemplate4.defineField(coordinates, -1, eft4)
                    element = mesh.createElement(elementIdentifier,
                                                 elementtemplate4)
                    nodeIdentifiers = [
                        1, bni4 + va, bni4 + vb, bni4 + no3 + va,
                        bni4 + no3 + vb
                    ]
                    result1 = element.setNodesByIdentifier(
                        eft4, nodeIdentifiers)
                    # set general linear map coefficients
                    radiansAround = va * radiansPerElementAround
                    radiansAroundNext = vb * radiansPerElementAround
                    scalefactors = [
                        -1.0,
                        math.cos(radiansAround),
                        math.sin(radiansAround), radiansPerElementAround,
                        math.cos(radiansAroundNext),
                        math.sin(radiansAroundNext), radiansPerElementAround,
                        math.cos(radiansIncline),
                        math.sin(radiansIncline),
                        math.cos(radiansInclineNext),
                        math.sin(radiansInclineNext)
                    ]
                    result2 = element.setScaleFactors(eft4, scalefactors)
                    # print('pyramid bottom element', elementIdentifier, result1, result2, nodeIdentifiers)
                    elementIdentifier = elementIdentifier + 1

            # create regular radial elements
            for e2 in range(1, elementsCountUp - 1):
                if e3 == 0:
                    for e1 in range(elementsCountAround):
                        # create central radial elements: 6 node wedges
                        va = e1
                        vb = (e1 + 1) % elementsCountAround
                        eft2 = tricubichermite.createEftWedgeRadial(
                            va * 100, vb * 100)
                        elementtemplate2.defineField(coordinates, -1, eft2)
                        element = mesh.createElement(elementIdentifier,
                                                     elementtemplate2)
                        bni2 = elementsCountUp + 1 + (e2 - 1) * no2 + 1
                        nodeIdentifiers = [
                            e3 + e2 + 1, e3 + e2 + 2, bni2 + va, bni2 + vb,
                            bni2 + va + elementsCountAround,
                            bni2 + vb + elementsCountAround
                        ]
                        result1 = element.setNodesByIdentifier(
                            eft2, nodeIdentifiers)
                        # set general linear map coefficients
                        radiansAround = va * radiansPerElementAround
                        radiansAroundNext = vb * radiansPerElementAround
                        scalefactors = [
                            -1.0,
                            math.cos(radiansAround),
                            math.sin(radiansAround), radiansPerElementAround,
                            math.cos(radiansAroundNext),
                            math.sin(radiansAroundNext),
                            radiansPerElementAround,
                            math.cos(radiansAround),
                            math.sin(radiansAround), radiansPerElementAround,
                            math.cos(radiansAroundNext),
                            math.sin(radiansAroundNext),
                            radiansPerElementAround
                        ]
                        result2 = element.setScaleFactors(eft2, scalefactors)
                        # print('axis element', elementIdentifier, result1, result2, nodeIdentifiers)
                        elementIdentifier = elementIdentifier + 1
                else:
                    # Regular elements
                    bni = rni + e3 * no3 + e2 * no2

                    for e1 in range(elementsCountAround):
                        element = mesh.createElement(elementIdentifier,
                                                     elementtemplate)
                        na = e1
                        nb = (e1 + 1) % elementsCountAround
                        nodeIdentifiers = [
                            bni + na, bni + nb, bni + no2 + na, bni + no2 + nb,
                            bni + no3 + na, bni + no3 + nb,
                            bni + no3 + no2 + na, bni + no3 + no2 + nb
                        ]
                        result = element.setNodesByIdentifier(
                            eft, nodeIdentifiers)
                        # print('regular element', elementIdentifier, result, nodeIdentifiers)
                        elementIdentifier = elementIdentifier + 1

            # Create elements on top pole
            radiansIncline = math.pi * 0.5 * e3 / elementsCountRadial
            radiansInclineNext = math.pi * 0.5 * (e3 + 1) / elementsCountRadial

            if e3 == 0:
                # # Create tetrahedron elements on the top pole
                bni3 = elementsCountUp + 1 + (elementsCountUp - 2) * no2 + 1

                for e1 in range(elementsCountAround):
                    va = e1
                    vb = (e1 + 1) % elementsCountAround
                    eft3 = tricubichermite.createEftTetrahedronTop(
                        va * 100, vb * 100, 100000)
                    elementtemplate3.defineField(coordinates, -1, eft3)
                    element = mesh.createElement(elementIdentifier,
                                                 elementtemplate3)
                    nodeIdentifiers = [
                        elementsCountUp, elementsCountUp + 1, bni3 + va,
                        bni3 + vb
                    ]
                    result1 = element.setNodesByIdentifier(
                        eft3, nodeIdentifiers)
                    # set general linear map coefficients
                    radiansAround = va * radiansPerElementAround
                    radiansAroundNext = vb * radiansPerElementAround
                    scalefactors = [
                        -1.0,
                        math.cos(radiansAround),
                        math.sin(radiansAround), radiansPerElementAround,
                        math.cos(radiansAroundNext),
                        math.sin(radiansAroundNext), radiansPerElementAround,
                        math.cos(radiansAround),
                        math.sin(radiansAround), radiansPerElementAround,
                        math.cos(radiansAroundNext),
                        math.sin(radiansAroundNext), radiansPerElementAround,
                        math.cos(radiansIncline),
                        math.sin(radiansIncline),
                        math.cos(radiansInclineNext),
                        math.sin(radiansInclineNext)
                    ]
                    result2 = element.setScaleFactors(eft3, scalefactors)
                    # print('Tetrahedron top element', elementIdentifier, result1, result2, nodeIdentifiers)
                    elementIdentifier = elementIdentifier + 1

            else:
                # Create pyramid elements on the top pole
                bni5 = elementsCountUp + 1 + (e3 - 1) * no3 + (
                    elementsCountUp - 2) * no2 + 1

                for e1 in range(elementsCountAround):
                    va = e1
                    vb = (e1 + 1) % elementsCountAround
                    eft5 = tricubichermite.createEftPyramidTop(
                        va * 100, vb * 100, 100000 + e3 * 2)

                    elementtemplate5.defineField(coordinates, -1, eft5)
                    element = mesh.createElement(elementIdentifier,
                                                 elementtemplate5)
                    nodeIdentifiers = [
                        bni5 + va, bni5 + vb, elementsCountUp + 1,
                        bni5 + no3 + va, bni5 + no3 + vb
                    ]
                    result1 = element.setNodesByIdentifier(
                        eft5, nodeIdentifiers)
                    # set general linear map coefficients
                    radiansAround = va * radiansPerElementAround
                    radiansAroundNext = vb * radiansPerElementAround
                    scalefactors = [
                        -1.0,
                        math.cos(radiansAround),
                        math.sin(radiansAround), radiansPerElementAround,
                        math.cos(radiansAroundNext),
                        math.sin(radiansAroundNext), radiansPerElementAround,
                        math.cos(radiansIncline),
                        math.sin(radiansIncline),
                        math.cos(radiansInclineNext),
                        math.sin(radiansInclineNext)
                    ]
                    result2 = element.setScaleFactors(eft5, scalefactors)
                    # print('pyramid top element', elementIdentifier, result1, result2, nodeIdentifiers)
                    elementIdentifier = elementIdentifier + 1

        fm.endChange()
        return []
Beispiel #12
0
    def generateMesh(region, options):
        """
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: None
        """
        lvWallThickness = options['LV wall thickness']
        baseHeight = options['Base height']
        baseThickness = options['Base thickness']
        lvOutletRadius = options['LV outlet inner diameter'] * 0.5
        lvOutletWallThickness = options['LV outlet wall thickness']
        rvOutletRadius = options['RV outlet inner diameter'] * 0.5
        rvOutletWallThickness = options['RV outlet wall thickness']
        outletElementLength = options['Outlet element length']
        useCrossDerivatives = False

        # generate default heart ventricles model to add base plane to
        heartVentriclesOptions = MeshType_3d_heartventricles2.getDefaultOptions(
        )
        for key in [
                'LV wall thickness', 'LV wall thickness ratio apex',
                'LV wall thickness ratio base', 'RV free wall thickness',
                'RV width', 'Length ratio', 'Element length ratio equator/apex'
        ]:
            heartVentriclesOptions[key] = options[key]
        MeshType_3d_heartventricles2.generateMesh(region,
                                                  heartVentriclesOptions)

        fm = region.getFieldmodule()
        fm.beginChange()
        # find the coordinates field created for the sphere shell
        coordinates = fm.findFieldByName('coordinates').castFiniteElement()

        nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
        nodetemplateFull = nodes.createNodetemplate()
        nodetemplateFull.defineField(coordinates)
        nodetemplateFull.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_VALUE, 1)
        nodetemplateFull.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1)
        nodetemplateFull.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1)
        nodetemplateFull.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D_DS3, 1)
        # nodes used only in bicubic-linear elements do not have D_DS3 parameters
        nodetemplateLinearS3 = nodes.createNodetemplate()
        nodetemplateLinearS3.defineField(coordinates)
        nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1,
                                                      Node.VALUE_LABEL_VALUE,
                                                      1)
        nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1,
                                                      Node.VALUE_LABEL_D_DS1,
                                                      1)
        nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1,
                                                      Node.VALUE_LABEL_D_DS2,
                                                      1)

        mesh = fm.findMeshByDimension(3)

        tricubichermite = eftfactory_tricubichermite(mesh, useCrossDerivatives)
        eft = tricubichermite.createEftBasic()

        elementtemplate = mesh.createElementtemplate()
        elementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        result = elementtemplate.defineField(coordinates, -1, eft)

        cache = fm.createFieldcache()

        # create nodes
        nodeIdentifier = startNodeIdentifier = getMaximumNodeIdentifier(
            nodes) + 1

        outletJoinRadians = math.pi  # *0.8
        cosOutletJoinRadians = math.cos(outletJoinRadians)
        sinOutletJoinRadians = math.sin(outletJoinRadians)
        lvOutletOffset = 0.5 - lvWallThickness - lvOutletRadius
        #lvOutletCentreX = cosOutletJoinRadians*lvOutletOffset
        #lvOutletCentreY = sinOutletJoinRadians*lvOutletOffset
        lvOutletCentreX = 0.0
        lvOutletCentreY = lvOutletOffset

        # LV outlet - for bicubic-linear tube connection
        elementsCountAround = 6
        radiansPerElementAround = 2.0 * math.pi / elementsCountAround
        x = [0.0, 0.0, baseHeight + baseThickness]
        dx_ds1 = [0.0, 0.0, 0.0]
        dx_ds2 = [0.0, 0.0, outletElementLength]
        dx_ds3 = [0.0, 0.0, 0.0]
        zero = [0.0, 0.0, 0.0]
        for n3 in range(2):
            radius = lvOutletRadius + lvOutletWallThickness * n3
            for n1 in range(elementsCountAround):
                radiansAround = outletJoinRadians - math.pi + n1 * radiansPerElementAround
                cosRadiansAround = math.cos(radiansAround)
                sinRadiansAround = math.sin(radiansAround)
                x[0] = lvOutletCentreX + radius * cosRadiansAround
                x[1] = lvOutletCentreY + radius * sinRadiansAround
                dx_ds1[
                    0] = radiansPerElementAround * radius * -sinRadiansAround
                dx_ds1[1] = radiansPerElementAround * radius * cosRadiansAround
                nodetemplate = nodetemplateLinearS3 if (
                    n3 == 0) else nodetemplateFull
                node = nodes.createNode(nodeIdentifier, nodetemplate)
                cache.setNode(node)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_VALUE, 1, x)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS1, 1,
                                              dx_ds1)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS2, 1,
                                              dx_ds2)
                if nodetemplate is nodetemplateFull:
                    dx_ds3[0] = lvOutletWallThickness * cosRadiansAround
                    dx_ds3[1] = lvOutletWallThickness * sinRadiansAround
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS3, 1,
                                                  dx_ds3)
                nodeIdentifier += 1

        # RV outlet - for bicubic-linear tube connection
        elementsCountAround = 6
        radiansPerElementAround = 2.0 * math.pi / elementsCountAround
        rvOutletOffset = lvOutletRadius + lvOutletWallThickness + rvOutletWallThickness + rvOutletRadius
        rvOutletCentreX = lvOutletCentreX + cosOutletJoinRadians * rvOutletOffset
        rvOutletCentreY = lvOutletCentreY + sinOutletJoinRadians * rvOutletOffset

        x = [0.0, 0.0, baseHeight + baseThickness]
        dx_ds1 = [0.0, 0.0, 0.0]
        dx_ds2 = [0.0, 0.0, outletElementLength]
        dx_ds3 = [0.0, 0.0, 0.0]
        for n3 in range(2):
            radius = rvOutletRadius + rvOutletWallThickness * n3
            for n1 in range(elementsCountAround):
                if (n3 == 1) and (n1 == 0):
                    continue  # node is common with LV outlet
                radiansAround = outletJoinRadians - math.pi + n1 * radiansPerElementAround
                cosRadiansAround = math.cos(radiansAround)
                sinRadiansAround = math.sin(radiansAround)
                x[0] = rvOutletCentreX + radius * cosRadiansAround
                x[1] = rvOutletCentreY + radius * sinRadiansAround
                dx_ds1[
                    0] = radiansPerElementAround * radius * -sinRadiansAround
                dx_ds1[1] = radiansPerElementAround * radius * cosRadiansAround
                node = nodes.createNode(nodeIdentifier, nodetemplateLinearS3)
                cache.setNode(node)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_VALUE, 1, x)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS1, 1,
                                              dx_ds1)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS2, 1,
                                              dx_ds2)
                nodeIdentifier += 1

        # create elements
        elementIdentifier = startElementIdentifier = getMaximumElementIdentifier(
            mesh) + 1

        rvNids = [[104, 105, 110, 111], [105, 106, 111, 112],
                  [106, 107, 112, 113], [107, 108, 113, 114],
                  [116, 117, 113, 122], [117, 118, 122, 123],
                  [118, 119, 123, 124], [119, 120, 124, 125]]

        scalefactors5hanging = [-1.0, 0.5, 0.25, 0.125, 0.75]

        i = -1
        for eid in range(55, 59):
            i += 1
            if i < 2:
                continue
            origElement = mesh.findElementByIdentifier(eid)
            origEft = origElement.getElementfieldtemplate(coordinates, -1)
            origNodeIdentifiers = getElementNodeIdentifiers(
                origElement, origEft)

            # first and last elements have scaling to use and fix (GRC to do)
            if False:  #eid == 55:
                eft1 = origEft
            else:
                eft1 = tricubichermite.createEftBasic()
            if False:  #eid == 58:
                eft2 = origEft
            else:
                eft2 = tricubichermite.createEftBasic()

            # general scale factors 1 -> 1, 102 -> 1/2, 104 -> 1/4, 108 -> 1/8, 304 -> 3/4
            setEftScaleFactorIds(eft1, [1, 102, 104, 108, 304], [])
            setEftScaleFactorIds(eft2, [1, 102, 104, 108, 304], [])

            tricubichermite.setEftMidsideXi1HangingNode(
                eft1, 2, 1, 1, 2, [1, 2, 3, 4, 5])
            tricubichermite.setEftMidsideXi1HangingNode(
                eft1, 6, 5, 5, 6, [1, 2, 3, 4, 5])
            tricubichermite.setEftLinearDerivativeXi3(eft1, 3, 7, 3, 7, 1)
            tricubichermite.setEftLinearDerivativeXi3(eft1, 4, 8, 4, 8, 1)

            tricubichermite.setEftMidsideXi1HangingNode(
                eft2, 1, 2, 1, 2, [1, 2, 3, 4, 5])
            tricubichermite.setEftMidsideXi1HangingNode(
                eft2, 5, 6, 5, 6, [1, 2, 3, 4, 5])
            tricubichermite.setEftLinearDerivativeXi3(eft2, 3, 7, 3, 7, 1)
            tricubichermite.setEftLinearDerivativeXi3(eft2, 4, 8, 4, 8, 1)

            elementtemplate1 = mesh.createElementtemplate()
            elementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE)
            result = elementtemplate1.defineField(coordinates, -1, eft1)
            element = mesh.createElement(elementIdentifier, elementtemplate1)
            nids = rvNids[i * 2]
            nodeIdentifiers1 = [
                origNodeIdentifiers[2], origNodeIdentifiers[3], nids[0],
                nids[1], origNodeIdentifiers[6], origNodeIdentifiers[7],
                nids[2], nids[3]
            ]
            result2 = element.setNodesByIdentifier(eft1, nodeIdentifiers1)
            result3 = element.setScaleFactors(eft1, scalefactors5hanging)
            print('create hanging element 1', elementIdentifier, result2,
                  result3, nodeIdentifiers1, scalefactors5hanging)
            elementIdentifier += 1

            elementtemplate2 = mesh.createElementtemplate()
            elementtemplate2.setElementShapeType(Element.SHAPE_TYPE_CUBE)
            result = elementtemplate2.defineField(coordinates, -1, eft2)
            element = mesh.createElement(elementIdentifier, elementtemplate2)
            nids = rvNids[i * 2 + 1]
            nodeIdentifiers2 = [
                origNodeIdentifiers[2], origNodeIdentifiers[3], nids[0],
                nids[1], origNodeIdentifiers[6], origNodeIdentifiers[7],
                nids[2], nids[3]
            ]
            result2 = element.setNodesByIdentifier(eft2, nodeIdentifiers2)
            result3 = element.setScaleFactors(eft2, scalefactors5hanging)
            print('create hanging element 2', elementIdentifier, result2,
                  result3, nodeIdentifiers2, scalefactors5hanging)
            elementIdentifier += 1

        fm.endChange()
def createAnnulusMesh3d(nodes, mesh, nextNodeIdentifier, nextElementIdentifier,
    startPointsx, startPointsd1, startPointsd2, startPointsd3, startNodeId, startDerivativesMap,
    endPointsx, endPointsd1, endPointsd2, endPointsd3, endNodeId, endDerivativesMap,
    forceStartLinearXi3 = False, forceMidLinearXi3 = False, forceEndLinearXi3 = False,
    maxStartThickness = None, maxEndThickness = None, useCrossDerivatives = False,
    elementsCountRadial = 1, meshGroups = []):
    '''
    Create an annulus mesh from a loop of start points/nodes with specified derivative mappings to
    a loop of end points/nodes with specified derivative mappings.
    Derivative d3 is through the wall. Currently limited to single element layer through wall.
    Points/nodes order cycles fastest around the annulus, then through the wall.
    Note doesn't support cross derivatives.
    Arrays are indexed by n3 (node through wall, size 2), n2 (node along/radial), n1 (node around, variable size)
    and coordinate component c.
    :param nodes: The nodeset to create nodes in.
    :param mesh: The mesh to create elements in.
    :param nextNodeIdentifier, nextElementIdentifier: Next identifiers to use and increment.
    :param startPointsx, startPointsd1, startPointsd2, startPointsd3, endPointsx, endPointsd1, endPointsd2, endPointsd3:
        List array[n3][n1][c] or start/point coordinates and derivatives. To linearise through the wall, pass None to d3.
        If both ends are linear through the wall, interior points are linear through the wall.
    :param startNodeId, endNodeId: List array [n3][n1] of existing node identifiers to use at start/end. Pass None for
        argument if no nodes are specified at end. These arguments are 'all or nothing'.
    :param startDerivativesMap, endDerivativesMap: List array[n3][n1] of mappings for d/dxi1, d/dxi2, d/dxi3 at start/end of form:
        ( (1, -1, 0), (1, 0, 0), None ) where the first tuple means d/dxi1 = d/ds1 - d/ds2. Only 0, 1 and -1 may be used.
        None means use default e.g. d/dxi2 = d/ds2.
        Pass None for the entire argument to use the defaults d/dxi1 = d/ds1, d/dxi2 = d/ds2, d/dxi3 = d/ds3.
        Pass a 4th mapping to apply to d/dxi1 on other side of node; if not supplied first mapping applies both sides.
    :param nodetemplate: Full tricubic Hermite node template, can omit cross derivatives.
    :param forceStartLinearXi3, forceMidLinearXi3, forceEndLinearXi3: Force start, middle or
        end elements to be linear through the wall, even if d3 is supplied at either end.
        Can only use forceMidLinearXi3 only if at least one end is linear in d3.
    :param maxStartThickness, maxEndThickness: Optional maximum override on start/end thicknesses.
    :param useCrossDerivatives: May only be True if no derivatives maps are in use.
    :param elementsCountRadial: Optional number of elements in radial direction between start and end.
    :param meshGroups:  Optional list of Zinc MeshGroup for adding new elements to.
    :return: Final values of nextNodeIdentifier, nextElementIdentifier
    '''
    assert (elementsCountRadial >= 1), 'createAnnulusMesh3d:  Invalid number of radial elements'
    startLinearXi3 = (not startPointsd3) or forceStartLinearXi3
    endLinearXi3 = (not endPointsd3) or forceEndLinearXi3
    midLinearXi3 = (startLinearXi3 and endLinearXi3) or ((startLinearXi3 or endLinearXi3) and forceMidLinearXi3)
    # get list whether each row of nodes in elements is linear in Xi3
    # this is for element use; start/end nodes may have d3 even if element is linear
    rowLinearXi3 = [ startLinearXi3 ] + [ midLinearXi3 ]*(elementsCountRadial - 1) + [ endLinearXi3 ]
    assert (not useCrossDerivatives) or ((not startDerivativesMap) and (not endDerivativesMap)), \
        'createAnnulusMesh3d:  Cannot use cross derivatives with derivatives map'
    elementsCountWall = 1
    nodesCountWall = elementsCountWall + 1
    assert (len(startPointsx) == nodesCountWall) and (len(startPointsd1) == nodesCountWall) and (len(startPointsd2) == nodesCountWall) and \
        (startLinearXi3 or (len(startPointsd3) == nodesCountWall)) and \
        (len(endPointsx) == nodesCountWall) and (len(endPointsd1) == nodesCountWall) and (len(endPointsd2) == nodesCountWall) and \
        (endLinearXi3 or (len(endPointsd3) == nodesCountWall)) and \
        ((startNodeId is None) or (len(startNodeId) == nodesCountWall)) and \
        ((endNodeId is None) or (len(endNodeId) == nodesCountWall)) and \
        ((startDerivativesMap is None) or (len(startDerivativesMap) == nodesCountWall)) and \
        ((endDerivativesMap is None) or (len(endDerivativesMap) == nodesCountWall)), \
        'createAnnulusMesh3d:  Mismatch in number of layers through wall'
    elementsCountAround = nodesCountAround = len(startPointsx[0])
    assert (nodesCountAround > 1), 'createAnnulusMesh3d:  Invalid number of points/nodes around annulus'
    for n3 in range(nodesCountWall):
        assert (len(startPointsx[n3]) == nodesCountAround) and (len(startPointsd1[n3]) == nodesCountAround) and (len(startPointsd2[n3]) == nodesCountAround) and \
            (startLinearXi3 or (len(startPointsd3[n3]) == nodesCountAround)) and \
            (len(endPointsx[n3]) == nodesCountAround) and (len(endPointsd1[n3]) == nodesCountAround) and (len(endPointsd2[n3]) == nodesCountAround) and \
            (endLinearXi3 or (len(endPointsd3[n3]) == nodesCountAround)) and \
            ((startNodeId is None) or (len(startNodeId[n3]) == nodesCountAround)) and \
            ((endNodeId is None) or (len(endNodeId[n3]) == nodesCountAround)) and \
            ((startDerivativesMap is None) or (len(startDerivativesMap[n3]) == nodesCountAround)) and \
            ((endDerivativesMap is None) or (len(endDerivativesMap[n3]) == nodesCountAround)), \
            'createAnnulusMesh3d:  Mismatch in number of points/nodes in layers through wall'

    fm = mesh.getFieldmodule()
    fm.beginChange()
    cache = fm.createFieldcache()
    coordinates = zinc_utils.getOrCreateCoordinateField(fm)

    # Build arrays of points from start to end
    px  = [ [], [] ]
    pd1 = [ [], [] ]
    pd2 = [ [], [] ]
    pd3 = [ [], [] ]
    for n3 in range(2):
        px [n3] = [ startPointsx [n3], endPointsx [n3] ]
        pd1[n3] = [ startPointsd1[n3], endPointsd1[n3] ]
        pd2[n3] = [ startPointsd2[n3], endPointsd2[n3] ]
        pd3[n3] = [ startPointsd3[n3] if (startPointsd3 is not None) else None, \
                    endPointsd3[n3] if (endPointsd3 is not None) else None ]
    if elementsCountRadial > 1:
        # add in-between points
        startPointsd = [ startPointsd1, startPointsd2, startPointsd3 ]
        startPointsdslimit = 2 if (startPointsd3 is None) else 3
        endPointsd = [ endPointsd1, endPointsd2, endPointsd3 ]
        endPointsdslimit = 2 if (endPointsd3 is None) else 3
        for n3 in range(2):
            for n2 in range(1, elementsCountRadial):
                px [n3].insert(n2, [ None ]*nodesCountAround)
                pd1[n3].insert(n2, [ None ]*nodesCountAround)
                pd2[n3].insert(n2, [ None ]*nodesCountAround)
                pd3[n3].insert(n2, None if midLinearXi3 else [ None ]*nodesCountAround)
        # compute on outside / n3 = 1, then map to inside using thickness
        thicknesses = []
        thicknesses.append([ vector.magnitude([ (startPointsx[1][n1][c] - startPointsx[0][n1][c]) for c in range(3) ]) for n1 in range(nodesCountAround) ])
        if maxStartThickness:
            for n1 in range(nodesCountAround):
                thicknesses[0][n1] = min(thicknesses[0][n1], maxStartThickness)
        for n2 in range(1, elementsCountRadial):
            thicknesses.append([ None ]*nodesCountAround)
        thicknesses.append([ vector.magnitude([ (endPointsx[1][n1][c] - endPointsx[0][n1][c]) for c in range(3) ]) for n1 in range(nodesCountAround) ])
        if maxEndThickness:
            for n1 in range(nodesCountAround):
                thicknesses[-1][n1] = min(thicknesses[-1][n1], maxEndThickness)
        n3 == 1
        for n1 in range(nodesCountAround):
            ax  = startPointsx [n3][n1]
            if (startDerivativesMap is None) or (startDerivativesMap[n3][n1][0] is None):
                ad1 = startPointsd1[n3][n1]
            else:
                derivativesMap = startDerivativesMap[n3][n1][0]
                ad1 = [ 0.0, 0.0, 0.0 ]
                for ds in range(startPointsdslimit):
                    if derivativesMap[ds] != 0.0:
                        for c in range(3):
                            ad1[c] += derivativesMap[ds]*startPointsd[ds][n3][n1][c]
                if len(startDerivativesMap[n3][n1]) > 3:
                    # average with d1 map for other side
                    derivativesMap = startDerivativesMap[n3][n1][3]
                    ad1 = [ 0.5*d for d in ad1 ]
                    if not derivativesMap:
                        for c in range(3):
                            ad1[c] += 0.5*startPointsd[0][n3][n1][c]
                    else:
                        for ds in range(startPointsdslimit):
                            if derivativesMap[ds] != 0.0:
                                for c in range(3):
                                    ad1[c] += 0.5*derivativesMap[ds]*startPointsd[ds][n3][n1][c]
            if (startDerivativesMap is None) or (startDerivativesMap[n3][n1][1] is None):
                ad2 = startPointsd2[n3][n1]
            else:
                derivativesMap = startDerivativesMap[n3][n1][1]
                ad2 = [ 0.0, 0.0, 0.0 ]
                for ds in range(startPointsdslimit):
                    if derivativesMap[ds] != 0.0:
                        for c in range(3):
                            ad2[c] += derivativesMap[ds]*startPointsd[ds][n3][n1][c]

            bx  = endPointsx [n3][n1]
            if (endDerivativesMap is None) or (endDerivativesMap[n3][n1][0] is None):
                bd1 = endPointsd1[n3][n1]
            else:
                derivativesMap = endDerivativesMap[n3][n1][0]
                bd1 = [ 0.0, 0.0, 0.0 ]
                for ds in range(endPointsdslimit):
                    if derivativesMap[ds] != 0.0:
                        for c in range(3):
                            bd1[c] += derivativesMap[ds]*endPointsd[ds][n3][n1][c]
                if len(endDerivativesMap[n3][n1]) > 3:
                    # average with d1 map for other side
                    derivativesMap = endDerivativesMap[n3][n1][3]
                    bd1 = [ 0.5*d for d in bd1 ]
                    if not derivativesMap:
                        for c in range(3):
                            bd1[c] += 0.5*endPointsd[0][n3][n1][c]
                    else:
                        for ds in range(endPointsdslimit):
                            if derivativesMap[ds] != 0.0:
                                for c in range(3):
                                    bd1[c] += 0.5*derivativesMap[ds]*endPointsd[ds][n3][n1][c]
            if (endDerivativesMap is None) or (endDerivativesMap[n3][n1][1] is None):
                bd2 = endPointsd2[n3][n1]
            else:
                derivativesMap = endDerivativesMap[n3][n1][1]
                bd2 = [ 0.0, 0.0, 0.0 ]
                for ds in range(endPointsdslimit):
                    if derivativesMap[ds] != 0.0:
                        for c in range(3):
                            bd2[c] += derivativesMap[ds]*endPointsd[ds][n3][n1][c]

            # scaling end derivatives to arc length gives even curvature along the curve
            arcLength = interp.computeCubicHermiteArcLength(ax, ad2, bx, bd2, rescaleDerivatives = False)
            scaledDerivatives = [ vector.setMagnitude(d2, arcLength) for d2 in [ ad2, bd2 ]]
            mx, md2, me, mxi = interp.sampleCubicHermiteCurvesSmooth([ ax, bx ], scaledDerivatives, elementsCountRadial,
                derivativeMagnitudeStart = vector.magnitude(ad2),
                derivativeMagnitudeEnd = vector.magnitude(bd2))[0:4]
            md1 = interp.interpolateSampleLinear([ ad1, bd1 ], me, mxi)
            thi = interp.interpolateSampleLinear([ thicknesses[0][n1], thicknesses[-1][n1] ], me, mxi)
            #md2 = interp.smoothCubicHermiteDerivativesLine(mx, md2, fixStartDerivative = True, fixEndDerivative = True)
            for n2 in range(1, elementsCountRadial):
                px [n3][n2][n1] = mx [n2]
                pd1[n3][n2][n1] = md1[n2]
                pd2[n3][n2][n1] = md2[n2]
                thicknesses[n2][n1] = thi[n2]

        # now get inner positions from normal and thickness, derivatives from curvature
        for n2 in range(1, elementsCountRadial):
            # first smooth derivative 1 around outer loop
            pd1[1][n2] = interp.smoothCubicHermiteDerivativesLoop(px[1][n2], pd1[1][n2], magnitudeScalingMode = interp.DerivativeScalingMode.HARMONIC_MEAN)

            for n1 in range(nodesCountAround):
                normal = vector.normalise(vector.crossproduct3(pd1[1][n2][n1], pd2[1][n2][n1]))
                thickness = thicknesses[n2][n1]
                d3 = [ d*thickness for d in normal ]
                px [0][n2][n1] = [ (px [1][n2][n1][c] - d3[c]) for c in range(3) ]
                # calculate inner d1 from curvature around
                n1m = n1 - 1
                n1p = (n1 + 1)%nodesCountAround
                curvature = 0.5*(
                    interp.getCubicHermiteCurvature(px[1][n2][n1m], pd1[1][n2][n1m], px[1][n2][n1 ], pd1[1][n2][n1 ], normal, 1.0) +
                    interp.getCubicHermiteCurvature(px[1][n2][n1 ], pd1[1][n2][n1 ], px[1][n2][n1p], pd1[1][n2][n1p], normal, 0.0))
                factor = 1.0 + curvature*thickness
                pd1[0][n2][n1] = [ factor*d for d in pd1[1][n2][n1] ]
                # calculate inner d2 from curvature radially
                n2m = n2 - 1
                n2p = n2 + 1
                curvature = 0.5*(
                    interp.getCubicHermiteCurvature(px[1][n2m][n1], pd2[1][n2m][n1], px[1][n2 ][n1], pd2[1][n2 ][n1], normal, 1.0) +
                    interp.getCubicHermiteCurvature(px[1][n2 ][n1], pd2[1][n2 ][n1], px[1][n2p][n1], pd2[1][n2p][n1], normal, 0.0))
                factor = 1.0 + curvature*thickness
                pd2[0][n2][n1] = [ factor*d for d in pd2[1][n2][n1] ]
                if not midLinearXi3:
                    pd3[0][n2][n1] = pd3[1][n2][n1] = d3

            # smooth derivative 1 around inner loop
            pd1[0][n2] = interp.smoothCubicHermiteDerivativesLoop(px[0][n2], pd1[0][n2], magnitudeScalingMode = interp.DerivativeScalingMode.HARMONIC_MEAN)

        for n3 in range(0, 1):  # was (0, nodesCountWall)
            # smooth derivative 2 radially/along annulus
            for n1 in range(nodesCountAround):
                sd2 = interp.smoothCubicHermiteDerivativesLine(
                    [ px [n3][n2][n1] for n2 in range(elementsCountRadial + 1) ],
                    [ pd2[n3][n2][n1] for n2 in range(elementsCountRadial + 1) ],
                    fixAllDirections = True, fixStartDerivative = True, fixEndDerivative = True,
                    magnitudeScalingMode = interp.DerivativeScalingMode.HARMONIC_MEAN)
                for n2 in range(elementsCountRadial + 1):
                    pd2[n3][n2][n1] = sd2[n2]

    ##############
    # Create nodes
    ##############

    nodetemplate = nodes.createNodetemplate()
    nodetemplate.defineField(coordinates)
    nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_VALUE, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS1, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS2, 1)
    if useCrossDerivatives:
        nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D2_DS1DS2, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS3, 1)
    if useCrossDerivatives:
        nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D2_DS1DS3, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D2_DS2DS3, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1)
    nodetemplateLinearS3 = nodes.createNodetemplate()
    nodetemplateLinearS3.defineField(coordinates)
    nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_VALUE, 1)
    nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS1, 1)
    nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS2, 1)
    if useCrossDerivatives:
        nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D2_DS1DS2, 1)

    nodeIdentifier = nextNodeIdentifier
    nodeId = [ [], [] ]
    for n3 in range(2):
        for n2 in range(elementsCountRadial + 1):
            if (n2 == 0) and (startNodeId is not None):
                rowNodeId = copy.deepcopy(startNodeId[n3])
            elif (n2 == elementsCountRadial) and (endNodeId is not None):
                rowNodeId = copy.deepcopy(endNodeId[n3])
            else:
                rowNodeId = []
                nodetemplate1 = nodetemplate if pd3[n3][n2] else nodetemplateLinearS3
                for n1 in range(nodesCountAround):
                    node = nodes.createNode(nodeIdentifier, nodetemplate1)
                    rowNodeId.append(nodeIdentifier)
                    cache.setNode(node)
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, px[n3][n2][n1])
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, pd1[n3][n2][n1])
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, pd2[n3][n2][n1])
                    if pd3[n3][n2]:
                        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, pd3[n3][n2][n1])
                    nodeIdentifier = nodeIdentifier + 1
            nodeId[n3].append(rowNodeId)

    #################
    # Create elements
    #################

    tricubichermite = eftfactory_tricubichermite(mesh, useCrossDerivatives)
    bicubichermitelinear = eftfactory_bicubichermitelinear(mesh, useCrossDerivatives)

    elementIdentifier = nextElementIdentifier

    elementtemplateStandard = mesh.createElementtemplate()
    elementtemplateStandard.setElementShapeType(Element.SHAPE_TYPE_CUBE)
    elementtemplateX = mesh.createElementtemplate()
    elementtemplateX.setElementShapeType(Element.SHAPE_TYPE_CUBE)

    for e2 in range(elementsCountRadial):
        nonlinearXi3 = (not rowLinearXi3[e2]) or (not rowLinearXi3[e2 + 1])
        eftFactory = tricubichermite if nonlinearXi3 else bicubichermitelinear
        eftStandard = eftFactory.createEftBasic()
        elementtemplateStandard.defineField(coordinates, -1, eftStandard)
        mapStartDerivatives = (e2 == 0) and (startDerivativesMap is not None)
        mapStartLinearDerivativeXi3 = nonlinearXi3 and rowLinearXi3[e2]
        mapEndDerivatives = (e2 == (elementsCountRadial - 1)) and (endDerivativesMap is not None)
        mapEndLinearDerivativeXi3 = nonlinearXi3 and rowLinearXi3[e2 + 1]
        mapDerivatives = mapStartDerivatives or mapStartLinearDerivativeXi3 or mapEndDerivatives or mapEndLinearDerivativeXi3
        for e1 in range(elementsCountAround):
            en = (e1 + 1)%elementsCountAround
            nids = [ nodeId[0][e2][e1], nodeId[0][e2][en], nodeId[0][e2 + 1][e1], nodeId[0][e2 + 1][en],
                     nodeId[1][e2][e1], nodeId[1][e2][en], nodeId[1][e2 + 1][e1], nodeId[1][e2 + 1][en] ]

            if mapDerivatives:
                eft1 = eftFactory.createEftNoCrossDerivatives()
                setEftScaleFactorIds(eft1, [1], [])
                if mapStartLinearDerivativeXi3:
                    eftFactory.setEftLinearDerivative(eft1, [ 1, 5 ], Node.VALUE_LABEL_D_DS3, 1, 5, 1)
                    eftFactory.setEftLinearDerivative(eft1, [ 2, 6 ], Node.VALUE_LABEL_D_DS3, 2, 6, 1)
                if mapStartDerivatives:
                    for i in range(2):
                        lns = [ 1, 5 ] if (i == 0) else [ 2, 6 ]
                        for n3 in range(2):
                            derivativesMap = startDerivativesMap[n3][e1] if (i == 0) else startDerivativesMap[n3][en]
                            # handle different d1 on each side of node
                            d1Map = derivativesMap[0] if ((i == 1) or (len(derivativesMap) < 4)) else derivativesMap[3]
                            d2Map = derivativesMap[1]
                            d3Map = derivativesMap[2]
                            # use temporary to safely swap DS1 and DS2:
                            ln = [ lns[n3] ]
                            if d1Map is not None:
                                remapEftNodeValueLabel(eft1, ln, Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D2_DS1DS2, [] ) ])
                            if d3Map is not None:
                                remapEftNodeValueLabel(eft1, ln, Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D2_DS2DS3, [] ) ])
                            if d2Map is not None:
                                remapEftNodeValueLabel(eft1, ln, Node.VALUE_LABEL_D_DS2, \
                                    derivativeSignsToExpressionTerms( ( Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS2, Node.VALUE_LABEL_D_DS3 ), d2Map))
                            if d1Map is not None:
                                remapEftNodeValueLabel(eft1, ln, Node.VALUE_LABEL_D2_DS1DS2, \
                                    derivativeSignsToExpressionTerms( ( Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS2, Node.VALUE_LABEL_D_DS3 ), d1Map))
                            if d3Map is not None:
                                remapEftNodeValueLabel(eft1, ln, Node.VALUE_LABEL_D2_DS2DS3, \
                                    derivativeSignsToExpressionTerms( ( Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS2, Node.VALUE_LABEL_D_DS3 ), d3Map))
                if mapEndLinearDerivativeXi3:
                    eftFactory.setEftLinearDerivative(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS3, 3, 7, 1)
                    eftFactory.setEftLinearDerivative(eft1, [ 4, 8 ], Node.VALUE_LABEL_D_DS3, 4, 8, 1)
                if mapEndDerivatives:
                    for i in range(2):
                        lns = [ 3, 7 ] if (i == 0) else [ 4, 8 ]
                        for n3 in range(2):
                            derivativesMap = endDerivativesMap[n3][e1] if (i == 0) else endDerivativesMap[n3][en]
                            # handle different d1 on each side of node
                            d1Map = derivativesMap[0] if ((i == 1) or (len(derivativesMap) < 4)) else derivativesMap[3]
                            d2Map = derivativesMap[1]
                            d3Map = derivativesMap[2]
                            # use temporary to safely swap DS1 and DS2:
                            ln = [ lns[n3] ]
                            if d1Map is not None:
                                remapEftNodeValueLabel(eft1, ln, Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D2_DS1DS2, [] ) ])
                            if d3Map is not None:
                                remapEftNodeValueLabel(eft1, ln, Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D2_DS2DS3, [] ) ])
                            if d2Map is not None:
                                remapEftNodeValueLabel(eft1, ln, Node.VALUE_LABEL_D_DS2, \
                                    derivativeSignsToExpressionTerms( ( Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS2, Node.VALUE_LABEL_D_DS3 ), d2Map))
                            if d1Map is not None:
                                remapEftNodeValueLabel(eft1, ln, Node.VALUE_LABEL_D2_DS1DS2, \
                                    derivativeSignsToExpressionTerms( ( Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS2, Node.VALUE_LABEL_D_DS3 ), d1Map))
                            if d3Map is not None:
                                remapEftNodeValueLabel(eft1, ln, Node.VALUE_LABEL_D2_DS2DS3, \
                                    derivativeSignsToExpressionTerms( ( Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS2, Node.VALUE_LABEL_D_DS3 ), d3Map))
                elementtemplateX.defineField(coordinates, -1, eft1)
                elementtemplate1 = elementtemplateX
            else:
                eft1 = eftStandard
                elementtemplate1 = elementtemplateStandard

            element = mesh.createElement(elementIdentifier, elementtemplate1)
            result2 = element.setNodesByIdentifier(eft1, nids)
            if mapDerivatives:
                result3 = element.setScaleFactors(eft1, [ -1.0 ])
            #else:
            #    result3 = '-'
            #print('create element annulus', element.isValid(), elementIdentifier, result2, result3, nids)
            elementIdentifier += 1

            for meshGroup in meshGroups:
                meshGroup.addElement(element)

    fm.endChange()

    return nodeIdentifier, elementIdentifier
    def generateBaseMesh(cls, region, options):
        """
        Generate the base tricubic Hermite mesh.
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: [] empty list of AnnotationGroup
        """
        elementsCountAlong = options['Number of elements along']
        elementsCountAcross = options['Number of elements across']
        wallThickness = [ options['Wall thickness left'], options['Wall thickness right'] ]
        flangeLength = options['Flange length']
        bulgeRadius = options['Bulge radius']
        useCrossDerivatives = options['Use cross derivatives']

        fm = region.getFieldmodule()
        fm.beginChange()
        coordinates = findOrCreateFieldCoordinates(fm)

        nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
        nodetemplate = nodes.createNodetemplate()
        nodetemplate.defineField(coordinates)
        nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_VALUE, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS1, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS2, 1)
        if useCrossDerivatives:
            nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D2_DS1DS2, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS3, 1)
        if useCrossDerivatives:
            nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D2_DS1DS3, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D2_DS2DS3, 1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1)

        mesh = fm.findMeshByDimension(3)

        tricubichermite = eftfactory_tricubichermite(mesh, useCrossDerivatives)
        eft = tricubichermite.createEftBasic()
        eftOuter = tricubichermite.createEftTubeSeptumOuter()
        eftInner1 = tricubichermite.createEftTubeSeptumInner1()
        eftInner2 = tricubichermite.createEftTubeSeptumInner2()

        elementtemplate = mesh.createElementtemplate()
        elementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        result = elementtemplate.defineField(coordinates, -1, eft)
        elementtemplateOuter = mesh.createElementtemplate()
        elementtemplateOuter.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        result = elementtemplateOuter.defineField(coordinates, -1, eftOuter)
        elementtemplateInner1 = mesh.createElementtemplate()
        elementtemplateInner1.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        result = elementtemplateInner1.defineField(coordinates, -1, eftInner1)
        elementtemplateInner2 = mesh.createElementtemplate()
        elementtemplateInner2.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        result = elementtemplateInner2.defineField(coordinates, -1, eftInner2)

        cache = fm.createFieldcache()

        # create nodes
        nodeIdentifier = 1
        #radiansPerElementAcross = math.pi/elementsCountAcross
        bevelAngle = math.pi/2
        sinBevelAngle = math.sin(bevelAngle)
        cosBevelAngle = math.cos(bevelAngle)

        x = [ 0.0, 0.0, 0.0 ]
        dx_ds1 = [ 0.0, 0.0, 0.0 ]
        dx_ds2 = [ 0.0, 0.0, 1.0 / elementsCountAlong ]
        dx_ds3 = [ 0.0, 0.0, 0.0 ]
        zero = [ 0.0, 0.0, 0.0 ]
        wallThicknessSeptum = wallThickness[0]
        #wallThicknessMin = min(wallThickness)
        wallThicknessMax = max(wallThickness)
        for n3 in range(2):
            sign = -1.0 if (n3 == 0) else 1.0
            radiusY = 0.5*wallThicknessSeptum
            radiusX = 0.5 - wallThicknessMax
            for n2 in range(elementsCountAlong + 1):
                x[2] = n2 / elementsCountAlong
                for n1 in range(elementsCountAcross + 3):
                    if (n1 == 0) or (n1 == (elementsCountAcross + 2)):
                        flip = -1.0 if (n1 == 0) else 1.0
                        x[0] = radiusX + wallThickness[n3]
                        if n1 == 0:
                            x[0] = -x[0]
                        x[1] = -sign*(radiusY + flangeLength)
                        #if wallThickness[0] > wallThickness[1]:
                        #    x[1] -= flangeLength
                        #elif wallThickness[0] < wallThickness[1]:
                        #    x[1] += flangeLength
                        dx_ds1[0] = 0.0
                        dx_ds1[1] = 2.0*flip*(radiusY + flangeLength)
                        dx_ds3[0] = flip*wallThickness[n3]
                        dx_ds3[1] = 0.0
                    elif (n1 == 1) or (n1 == (elementsCountAcross + 1)):
                        flip = -1.0 if (n1 == 1) else 1.0
                        radius = radiusX
                        x[0] = flip*radius
                        x[1] = -sign*(radiusY + flangeLength)
                        #dx_ds1[0] = -sign*2.0*radiusX/elementsCountAcross*cosBevelAngle
                        #dx_ds1[1] = 2.0*radiusX/elementsCountAcross*sinBevelAngle
                        mag1x = radiusX/elementsCountAcross
                        mag1y = flangeLength
                        #mag1min = radiusX/elementsCountAcross + flangeLength + math.sqrt(mag1x*mag1x + mag1y*mag1y)
                        mag1min = 2.0*math.sqrt(mag1x*mag1x + mag1y*mag1y)
                        mag1max = 2.0*(radiusY + flangeLength)
                        mag1 = mag1min if (mag1min < mag1max) else mag1max
                        dx_ds1[0] = -sign*mag1*cosBevelAngle
                        dx_ds1[1] = mag1*sinBevelAngle
                        #if ((n3 == 1) and (wallThickness[0] > wallThickness[1])) or \
                        #    ((n3 == 0) and (wallThickness[0] < wallThickness[1])):
                        dx_ds3[0] = wallThickness[n3]
                        dx_ds3[1] = 0.0
                        #else:
                        #    dx_ds3[0] = wallThickness[n3]*sinBevelAngle
                        #    dx_ds3[1] = sign*wallThickness[n3]*cosBevelAngle
                        if n1 == 1:
                            dx_ds1[1] = -dx_ds1[1]
                            dx_ds3[0] = -dx_ds3[0]
                    else:
                        f1 = (n1 - 1)/elementsCountAcross
                        f2 = 1.0 - f1
                        x[0] = (f1 - f2)*radiusX
                        x[1] = -sign*radiusY
                        dx_ds1[0] = 2.0*radiusX/elementsCountAcross
                        dx_ds1[1] = 0.0
                        dx_ds3[0] = 0.0
                        dx_ds3[1] = -wallThicknessSeptum
                    node = nodes.createNode(nodeIdentifier, nodetemplate)
                    cache.setNode(node)
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, x)
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, dx_ds1)
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, dx_ds2)
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, dx_ds3)
                    if useCrossDerivatives:
                        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D2_DS1DS2, 1, zero)
                        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D2_DS1DS3, 1, zero)
                        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D2_DS2DS3, 1, zero)
                        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1, zero)
                    nodeIdentifier = nodeIdentifier + 1

        rimCrossAngle = math.pi/4
        sinRimCrossAngle = math.sin(rimCrossAngle)
        cosRimCrossAngle = math.cos(rimCrossAngle)
        scaleFactorsOuter = [
            cosRimCrossAngle, sinRimCrossAngle, cosRimCrossAngle, -sinRimCrossAngle,
            cosRimCrossAngle, sinRimCrossAngle, cosRimCrossAngle, -sinRimCrossAngle
        ]
        scaleFactorsInner1 = [ -1.0, 4.0,
            cosRimCrossAngle, sinRimCrossAngle, cosRimCrossAngle, sinRimCrossAngle,
            cosRimCrossAngle, -sinRimCrossAngle, cosRimCrossAngle, -sinRimCrossAngle
        ]
        scaleFactorsInner2 = [ -1.0, 4.0,
            cosRimCrossAngle, -sinRimCrossAngle, cosRimCrossAngle, -sinRimCrossAngle,
            cosRimCrossAngle, sinRimCrossAngle, cosRimCrossAngle, sinRimCrossAngle
        ]

        # create elements
        elementIdentifier = 1
        rno = elementsCountAcross + 3
        wno = (elementsCountAlong + 1)*rno
        for e2 in range(elementsCountAlong):
            bn = e2*rno
            element = mesh.createElement(elementIdentifier, elementtemplateOuter)
            bni11 = bn + 2
            bni12 = bn + wno + 2
            bni21 = bn + rno + 2
            bni22 = bn + wno + rno + 2
            nodeIdentifiers = [ bni11, bni12, bni21, bni22, bni11 - 1, bni12 - 1, bni21 - 1, bni22 - 1 ]
            result = element.setNodesByIdentifier(eftOuter, nodeIdentifiers)
            #print(result, 'element', elementIdentifier, nodeIdentifiers)
            element.setScaleFactors(eftOuter, scaleFactorsOuter)
            elementIdentifier = elementIdentifier + 1

            element = mesh.createElement(elementIdentifier, elementtemplateInner1)
            bni11 = bn + 2
            bni12 = bn + 3
            bni21 = bn + rno + 2
            bni22 = bn + rno + 3
            nodeIdentifiers = [ bni11, bni12, bni21, bni22, bni11 + wno, bni12 + wno, bni21 + wno, bni22 + wno ]
            result = element.setNodesByIdentifier(eftInner1, nodeIdentifiers)
            #print(result, 'element', elementIdentifier, 'nodes', nodeIdentifiers)
            element.setScaleFactors(eftInner1, scaleFactorsInner1)
            #print(result, 'element', elementIdentifier, 'scale factors', scaleFactorsInner1)
            elementIdentifier = elementIdentifier + 1

            for e1 in range(elementsCountAcross - 2):
                element = mesh.createElement(elementIdentifier, elementtemplate)
                bni11 = bn + e1 + 3
                bni12 = bn + e1 + 4
                bni21 = bn + rno + e1 + 3
                bni22 = bn + rno + e1 + 4
                nodeIdentifiers = [ bni11, bni12, bni21, bni22, bni11 + wno, bni12 + wno, bni21 + wno, bni22 + wno ]
                result = element.setNodesByIdentifier(eft, nodeIdentifiers)
                #print(result, 'element', elementIdentifier, 'nodes', nodeIdentifiers)
                elementIdentifier = elementIdentifier + 1

            element = mesh.createElement(elementIdentifier, elementtemplateInner2)
            bni11 = bn + rno - 2
            bni12 = bn + rno - 1
            bni21 = bn + 2*rno -2
            bni22 = bn + 2*rno - 1
            nodeIdentifiers = [ bni11, bni12, bni21, bni22, bni11 + wno, bni12 + wno, bni21 + wno, bni22 + wno ]
            result = element.setNodesByIdentifier(eftInner2, nodeIdentifiers)
            #print(result, 'element', elementIdentifier, 'nodes', nodeIdentifiers)
            element.setScaleFactors(eftInner2, scaleFactorsInner2)
            #print(result, 'element', elementIdentifier, 'scale factors', scaleFactorsInner1)
            elementIdentifier = elementIdentifier + 1

            element = mesh.createElement(elementIdentifier, elementtemplateOuter)
            bni11 = bn + wno + rno - 1
            bni12 = bn + rno - 1
            bni21 = bn + wno + 2*rno - 1
            bni22 = bn + 2*rno - 1
            nodeIdentifiers = [ bni11, bni12, bni21, bni22, bni11 + 1, bni12 + 1, bni21 + 1, bni22 + 1 ]
            result = element.setNodesByIdentifier(eftOuter, nodeIdentifiers)
            #print(result, 'element', elementIdentifier, nodeIdentifiers)
            element.setScaleFactors(eftOuter, scaleFactorsOuter)
            elementIdentifier = elementIdentifier + 1

        if bulgeRadius != 0.0:
            # cylindrical polar coordinates:
            # r = y - bulgeRadius
            # theta = -x / bulgeRadius
            # z = z
            yxzCoordinates = fm.createFieldComponent(coordinates, [2, 1, 3])
            scale = fm.createFieldConstant([1.0, -1.0/bulgeRadius, 1.0 ])
            scaleCoordinates = fm.createFieldMultiply(yxzCoordinates, scale)
            offset = fm.createFieldConstant([-bulgeRadius, 0.0, 0.0 ])
            polarCoordinates = fm.createFieldAdd(scaleCoordinates, offset)
            polarCoordinates.setCoordinateSystemType(Field.COORDINATE_SYSTEM_TYPE_CYLINDRICAL_POLAR)
            rcCoordinates = fm.createFieldCoordinateTransformation(polarCoordinates)
            rcCoordinates.setCoordinateSystemType(Field.COORDINATE_SYSTEM_TYPE_RECTANGULAR_CARTESIAN)
            newyxzCoordinates = fm.createFieldSubtract(rcCoordinates, offset)
            newCoordinates = fm.createFieldComponent(newyxzCoordinates, [2, 1, 3])
            fieldassignment = coordinates.createFieldassignment(newCoordinates)
            result = fieldassignment.assign()

        fm.endChange()
        return []
    def generateBaseMesh(region, options):
        """
        Generate the base tricubic Hermite mesh. See also generateMesh().
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: None
        """
        elementsCountAround = options['Number of elements around']
        elementsCountUp = options['Number of elements up']
        elementsCountThroughLVWall = options['Number of elements through LV wall']
        elementsCountAcrossSeptum = options['Number of elements across septum']
        elementsCountBelowSeptum = options['Number of elements below septum']
        LVWallThickness = options['LV wall thickness']
        LVWallThicknessRatioApex = options['LV wall thickness ratio apex']
        LVWallThicknessRatioBase = options['LV wall thickness ratio base']
        LVBaseFlattenRatio = options['LV base flatten ratio']
        LVBaseFlattenAngleRadians = options['LV base flatten angle degrees']*math.pi/180.0

        lengthRatio = options['Length ratio']
        RVWidthTop = options['RV width']
        RVFreeWallThickness = options['RV free wall thickness']
        septumArcAngleRadians = options['Septum arc angle degrees']*math.pi/180.0
        useCrossDerivatives = options['Use cross derivatives']

        # generate a half sphere shell which will be edited
        sphereShellOptions = MeshType_3d_sphereshell1.getDefaultOptions()
        sphereShellOptions['Number of elements up'] = elementsCountUp*2
        sphereShellOptions['Number of elements around'] = elementsCountAround
        sphereShellOptions['Number of elements through wall'] = elementsCountThroughLVWall
        sphereShellOptions['Exclude top rows'] = elementsCountUp
        sphereShellOptions['Wall thickness'] = LVWallThickness
        sphereShellOptions['Wall thickness ratio apex'] = LVWallThicknessRatioApex
        sphereShellOptions['Length ratio'] = lengthRatio
        sphereShellOptions['Element length ratio equator/apex'] = options['Element length ratio equator/apex']
        MeshType_3d_sphereshell1.generateBaseMesh(region, sphereShellOptions)

        fm = region.getFieldmodule()
        fm.beginChange()
        coordinates = getOrCreateCoordinateField(fm)

        nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
        nodetemplate = nodes.createNodetemplate()
        nodetemplate.defineField(coordinates)
        nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_VALUE, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS1, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS2, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS3, 1)

        mesh = fm.findMeshByDimension(3)

        cache = fm.createFieldcache()

        nor = elementsCountAround
        now = 1 + elementsCountUp*nor

        # Resize elements around LV to get desired septum arc angle
        radiansPerElementOrig = 2.0*math.pi/elementsCountAround
        radiansPerElementSeptum = septumArcAngleRadians/elementsCountAcrossSeptum
        # want LV-RV 'transition' elements to be mean size of Septum and LV FreeWall elements
        radiansRemaining = 2.0*math.pi - septumArcAngleRadians
        elementsCountAroundLVFreeWall = elementsCountAround - elementsCountAcrossSeptum - 2
        radiansPerElementLVFreeWall = (radiansRemaining - radiansPerElementSeptum) / (elementsCountAroundLVFreeWall + 1)
        radiansPerElementTransition = 0.5*(radiansPerElementSeptum + radiansPerElementLVFreeWall)
        #print('Element size ratio LVFreeWall / Septum', radiansPerElementLVFreeWall/radiansPerElementSeptum)
        xyz_scale = fm.createFieldConstant([1.0, 1.0, lengthRatio/2.0 - LVWallThickness*LVWallThicknessRatioApex])
        coordinates_scale = fm.createFieldMultiply(coordinates, xyz_scale)
        sp = fm.createFieldCoordinateTransformation(coordinates_scale)
        sp.setCoordinateSystemType(Field.COORDINATE_SYSTEM_TYPE_SPHERICAL_POLAR)
        r = fm.createFieldComponent(sp, 1)
        theta = fm.createFieldComponent(sp, 2)
        phi = fm.createFieldComponent(sp, 3)
        zero = fm.createFieldConstant([0.0])
        one = fm.createFieldConstant([1.0])  # also used as 'true'
        two_pi = fm.createFieldConstant([2.0*math.pi])
        radians_per_element_orig = fm.createFieldConstant([radiansPerElementOrig])
        half_radians_per_element_orig = fm.createFieldConstant([0.5*radiansPerElementOrig])
        radians_per_element_transition = fm.createFieldConstant([radiansPerElementTransition])

        theta_continuous = fm.createFieldIf(fm.createFieldLessThan(theta, half_radians_per_element_orig), fm.createFieldAdd(theta, two_pi), theta)
        theta_offset = fm.createFieldSubtract(theta_continuous, radians_per_element_orig)
        theta_offset_septum_start = fm.createFieldConstant([-0.5*radiansPerElementOrig])
        theta_offset_septum_end = fm.createFieldConstant([(elementsCountAcrossSeptum + 0.5)*radiansPerElementOrig])
        in_septum = fm.createFieldAnd(fm.createFieldGreaterThan(theta_offset, theta_offset_septum_start), fm.createFieldLessThan(theta_offset, theta_offset_septum_end))
        septum_scale = fm.createFieldConstant([radiansPerElementSeptum/radiansPerElementOrig])
        thetaNewSeptumStart = radiansPerElementOrig + (radiansPerElementOrig - radiansPerElementSeptum)*elementsCountAcrossSeptum/2.0
        theta_new_septum_start = fm.createFieldConstant([thetaNewSeptumStart])
        theta_new_septum = fm.createFieldAdd(fm.createFieldMultiply(theta_offset, septum_scale), theta_new_septum_start)
        lvfreewall_scale = fm.createFieldConstant([radiansPerElementLVFreeWall/radiansPerElementOrig])
        theta_offset_lvfreewall_start = fm.createFieldConstant([radiansPerElementOrig*(elementsCountAcrossSeptum + 1.0)])
        theta_new_lvfreewall_start = fm.createFieldConstant([thetaNewSeptumStart + septumArcAngleRadians + radiansPerElementTransition])
        theta_new_lvfreewall = fm.createFieldAdd(
            fm.createFieldMultiply(fm.createFieldSubtract(theta_offset, theta_offset_lvfreewall_start), lvfreewall_scale),
            theta_new_lvfreewall_start)
        theta_new = fm.createFieldIf(in_septum, theta_new_septum, theta_new_lvfreewall)

        sp_new = fm.createFieldConcatenate([r, theta_new, phi])
        sp_new.setCoordinateSystemType(Field.COORDINATE_SYSTEM_TYPE_SPHERICAL_POLAR)
        coordinates_new_scale = fm.createFieldCoordinateTransformation(sp_new)
        coordinates_new_scale.setCoordinateSystemType(Field.COORDINATE_SYSTEM_TYPE_RECTANGULAR_CARTESIAN)
        coordinates_new = fm.createFieldDivide(coordinates_new_scale, xyz_scale)
        nonApexNodesetGroupField = fm.createFieldNodeGroup(nodes)
        nonApexNodesetGroup = nonApexNodesetGroupField.getNodesetGroup()
        nonApexNodesetGroup.addNodesConditional(one)  # add all
        # remove apex nodes:
        for i in range(elementsCountThroughLVWall + 1):
            node = nodes.findNodeByIdentifier(i*now + 1)
            nonApexNodesetGroup.removeNode(node)
        fieldassignment = coordinates.createFieldassignment(coordinates_new)
        fieldassignment.setNodeset(nonApexNodesetGroup)
        fieldassignment.assign()

        baseNodesetGroup = None
        baseNodeGroupField = fm.createFieldNodeGroup(nodes)
        z = fm.createFieldComponent(coordinates, 3)
        is_base = fm.createFieldGreaterThan(z, fm.createFieldConstant([-0.0001]))
        baseNodesetGroup = baseNodeGroupField.getNodesetGroup()
        baseNodesetGroup.addNodesConditional(is_base)
        #print('baseNodesetGroup.getSize()', baseNodesetGroup.getSize())

        if LVWallThicknessRatioBase != 1.0:
            # make LV walls thinner at base
            # get inside node at middle of RV
            now = 1 + elementsCountUp*elementsCountAround
            midRVnid = now - elementsCountAround + 2 + (elementsCountAcrossSeptum // 2)
            #print('midRVnid', midRVnid)
            midRVnode = nodes.findNodeByIdentifier(midRVnid)
            cp_coordinates = fm.createFieldCoordinateTransformation(coordinates)
            cp_coordinates.setCoordinateSystemType(Field.COORDINATE_SYSTEM_TYPE_CYLINDRICAL_POLAR)
            radius = fm.createFieldComponent(cp_coordinates, 1)
            theta = fm.createFieldComponent(cp_coordinates, 2)
            z = fm.createFieldComponent(cp_coordinates, 3)
            cache.setNode(midRVnode)
            #result, cp = cp_coordinates.evaluateReal(cache, 3)
            #coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, 3)
            result, innerRadius = radius.evaluateReal(cache, 1)
            #print('innerRadius', innerRadius)
            ir = fm.createFieldConstant([innerRadius*0.9999])
            radius_gt_ir = fm.createFieldGreaterThan(radius, ir)
            radius_minus_ir = fm.createFieldSubtract(radius, ir)
            thickness_scale = fm.createFieldConstant([LVWallThicknessRatioBase])
            delta_radius = fm.createFieldMultiply(radius_minus_ir, thickness_scale)
            new_radius = fm.createFieldAdd(ir, delta_radius)
            new_cp_coordinates = fm.createFieldConcatenate([new_radius, theta, z])
            new_cp_coordinates.setCoordinateSystemType(Field.COORDINATE_SYSTEM_TYPE_CYLINDRICAL_POLAR)
            new_coordinates = fm.createFieldCoordinateTransformation(new_cp_coordinates)
            new_coordinates.setCoordinateSystemType(Field.COORDINATE_SYSTEM_TYPE_RECTANGULAR_CARTESIAN)

            fieldassignment = coordinates.createFieldassignment(new_coordinates)
            result = fieldassignment.setNodeset(baseNodesetGroup)
            fieldassignment.assign()

        if LVBaseFlattenRatio != 1.0:
            # flatten LV normal to mid-RV-aorta-mitral axis plus additional flatten angle
            septumCentreRadians = (1.0 + elementsCountAcrossSeptum/2.0)*radiansPerElementOrig
            flattenAxisRadians = septumCentreRadians + LVBaseFlattenAngleRadians - 0.5*math.pi

            half = fm.createFieldConstant([0.5])
            minus_one = fm.createFieldConstant([-1.0])
            minus_two = fm.createFieldConstant([-2.0])

            # flatten ratio = new inner radius / original inner radius
            beta_base = fm.createFieldConstant([1.0 - LVBaseFlattenRatio])
            z = fm.createFieldComponent(coordinates, 3)
            z2 = fm.createFieldMultiply(z, z)
            zero_dist_node = nodes.findNodeByIdentifier(now - nor)
            cache.setNode(zero_dist_node)
            result, z_zero_dist_value = z.evaluateReal(cache, 1)
            #print('zero dist', result, z_zero_dist_value)
            z_zerodist = fm.createFieldConstant([z_zero_dist_value])
            z_zerodist2 = fm.createFieldMultiply(z_zerodist, z_zerodist)
            z_a = fm.createFieldDivide(one, z_zerodist2)
            z_b = fm.createFieldDivide(minus_two, z_zerodist)
            zfact = fm.createFieldAdd(fm.createFieldAdd(fm.createFieldMultiply(z_a, z2), fm.createFieldMultiply(z_b, z)), one)
            beta = fm.createFieldMultiply(zfact, beta_base)  # 1 - squash factor
            alpha = fm.createFieldSubtract(one, beta)  # z-dependent squash factor

            psi = fm.createFieldConstant([flattenAxisRadians])
            ri = fm.createFieldConstant([0.5 - LVWallThickness])
            theta_minus_psi = fm.createFieldSubtract(theta, psi)
            cos_theta_minus_psi = fm.createFieldCos(theta_minus_psi)
            sin_theta_minus_psi = fm.createFieldSin(theta_minus_psi)
            r_minus_ri = fm.createFieldSubtract(r, ri)

            rf = fm.createFieldAdd(fm.createFieldMultiply(alpha, r), fm.createFieldMultiply(beta, r_minus_ri))
            x_new = fm.createFieldMultiply(rf, cos_theta_minus_psi)
            y_new = fm.createFieldMultiply(r, sin_theta_minus_psi)
            r2_new = fm.createFieldAdd(fm.createFieldMultiply(x_new, x_new), fm.createFieldMultiply(y_new, y_new))
            r_new = fm.createFieldSqrt(r2_new)
            theta_minus_psi_raw = fm.createFieldAtan2(y_new, x_new)
            theta_wrap = fm.createFieldAnd(
                fm.createFieldLessThan(theta_minus_psi, minus_one),
                fm.createFieldGreaterThan(theta_minus_psi_raw, one))
            theta_minus_psi_fix = fm.createFieldIf(theta_wrap, fm.createFieldAdd(theta_minus_psi, two_pi), theta_minus_psi)
            # above theta is too great; average with theta_minus_psi_raw
            theta_minus_psi_new = fm.createFieldMultiply(half, fm.createFieldAdd(theta_minus_psi_fix, theta_minus_psi_raw))
            theta_new = fm.createFieldAdd(theta_minus_psi_new, psi)
            sp_new = fm.createFieldConcatenate([r_new, theta_new, phi])
            sp_new.setCoordinateSystemType(Field.COORDINATE_SYSTEM_TYPE_SPHERICAL_POLAR)
            coordinates_new_scale = fm.createFieldCoordinateTransformation(sp_new)
            coordinates_new_scale.setCoordinateSystemType(Field.COORDINATE_SYSTEM_TYPE_RECTANGULAR_CARTESIAN)
            coordinates_new = fm.createFieldDivide(coordinates_new_scale, xyz_scale)

            fieldassignment = coordinates.createFieldassignment(coordinates_new)
            result = fieldassignment.setNodeset(baseNodesetGroup)
            fieldassignment.assign()

        baseNodesetGroup = None

        tricubichermite = eftfactory_tricubichermite(mesh, useCrossDerivatives)
        tricubicHermiteBasis = fm.createElementbasis(3, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE)

        eft = tricubichermite.createEftBasic()
        elementtemplate = mesh.createElementtemplate()
        elementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        elementtemplate.defineField(coordinates, -1, eft)

        crossAngle = math.pi/8
        sinCrossAngle = math.sin(crossAngle)
        cosCrossAngle = math.cos(crossAngle)
        edgeRotateCrossAngle = math.pi/24
        sinEdgeRotateCrossAngle = math.sin(edgeRotateCrossAngle)
        cosEdgeRotateCrossAngle = math.cos(edgeRotateCrossAngle)

        # create RV nodes and modify adjoining LV nodes
        elementsCountUpRV = elementsCountUp - elementsCountBelowSeptum
        lv_bni_base = 3 + elementsCountThroughLVWall*now + (elementsCountBelowSeptum - 1)*elementsCountAround
        nodeIdentifier = startNodeIdentifier = getMaximumNodeIdentifier(nodes) + 1
        baseExtraRVWidth = LVWallThickness*(1.0 - LVWallThicknessRatioBase)
        rv_nids = []
        for n3 in range(2):  # only 1 element through RV free wall

            onInside = n3 == 0

            for n2 in range(elementsCountUpRV + 1):

                #if n2 > (elementsCountUpRV - elementsCountUpBase):
                #    theta = math.pi*0.5
                #else:
                #    theta = (n2 / (elementsCountUpRV - elementsCountUpBase))*math.pi*0.5
                #theta = (n2 / elementsCountUpRV)*math.pi*0.5
                RVWidth = RVWidthTop  #*math.sin(theta)
                #RVWidth = RVWidthTop

                #if n2 == 0:
                #    edgeOffsetInner = RVWidth*0.25
                #else:
                edgeOffsetInner = RVWidth*0.65
                if n2 == elementsCountUpRV:
                    RVWidth += baseExtraRVWidth
                    edgeOffsetInner = edgeOffsetInner + baseExtraRVWidth*0.5

                onBottomEdge = (n2 == 0)

                for n1 in range(elementsCountAcrossSeptum + 1):

                    onSideEdge1 = (n1 == 0)
                    onSideEdge2 = (n1 == elementsCountAcrossSeptum)
                    onSideEdge = onSideEdge1 or onSideEdge2

                    existingNodeIdentifier = lv_bni_base + n2*nor + n1

                    baseNode = nodes.findNodeByIdentifier(existingNodeIdentifier)
                    cache.setNode(baseNode)
                    result, base_x = coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, 3)
                    result, base_dx_ds1 = coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, 3)
                    result, base_dx_ds2 = coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, 3)
                    result, base_dx_ds3 = coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, 3)
                    #print('node ', existingNodeIdentifier, 'dx_ds3', result, base_dx_ds3)
                    mag = math.sqrt(base_dx_ds3[0]*base_dx_ds3[0] + base_dx_ds3[1]*base_dx_ds3[1] + base_dx_ds3[2]*base_dx_ds3[2])
                    unitOutward = [ base_dx_ds3[0]/mag, base_dx_ds3[1]/mag, base_dx_ds3[2]/mag ]
                    baseRadiusAround = unitOutward[0]*base_x[0] + unitOutward[1]*base_x[1] + unitOutward[2]*base_x[2]
                    rotateEdge = False
                    if onInside:
                        if onBottomEdge and onSideEdge:
                            offset = 0.0
                        elif onBottomEdge or onSideEdge:
                            offset = edgeOffsetInner
                            rotateEdge = True
                        else:
                            offset = RVWidth
                    else:
                        if onBottomEdge and onSideEdge:
                            offset = RVFreeWallThickness
                            #rotateEdge = True
                        elif onBottomEdge or onSideEdge:
                            offset = edgeOffsetInner  # + RVFreeWallThickness
                            rotateEdge = True
                        else:
                            offset = RVWidth + RVFreeWallThickness
                    x = [
                        base_x[0] + unitOutward[0]*offset,
                        base_x[1] + unitOutward[1]*offset,
                        base_x[2] + unitOutward[2]*offset,
                    ]
                    scale1 = (baseRadiusAround + offset)/baseRadiusAround
                    dx_ds1 = [
                        base_dx_ds1[0]*scale1,
                        base_dx_ds1[1]*scale1,
                        base_dx_ds1[2]*scale1
                    ]
                    # GRC not sure if appropriate to use scale1 here:
                    dx_ds2 = [
                        base_dx_ds2[0]*scale1,
                        base_dx_ds2[1]*scale1,
                        base_dx_ds2[2]*scale1
                    ]
                    scale3 = RVFreeWallThickness
                    dx_ds3 = [
                        unitOutward[0]*scale3,
                        unitOutward[1]*scale3,
                        unitOutward[2]*scale3
                    ]

                    if rotateEdge:
                        if onSideEdge1:
                            rotatedOutward = [
                                cosEdgeRotateCrossAngle*base_dx_ds3[0] - sinEdgeRotateCrossAngle*base_dx_ds1[0],
                                cosEdgeRotateCrossAngle*base_dx_ds3[1] - sinEdgeRotateCrossAngle*base_dx_ds1[1],
                                cosEdgeRotateCrossAngle*base_dx_ds3[2] - sinEdgeRotateCrossAngle*base_dx_ds1[2]
                            ]
                        elif onSideEdge2:
                            rotatedOutward = [
                                cosEdgeRotateCrossAngle*base_dx_ds3[0] + sinEdgeRotateCrossAngle*base_dx_ds1[0],
                                cosEdgeRotateCrossAngle*base_dx_ds3[1] + sinEdgeRotateCrossAngle*base_dx_ds1[1],
                                cosEdgeRotateCrossAngle*base_dx_ds3[2] + sinEdgeRotateCrossAngle*base_dx_ds1[2]
                            ]
                        else:  # onBottomEdge:
                            rotatedOutward = [
                                cosEdgeRotateCrossAngle*base_dx_ds3[0] - sinEdgeRotateCrossAngle*base_dx_ds2[0],
                                cosEdgeRotateCrossAngle*base_dx_ds3[1] - sinEdgeRotateCrossAngle*base_dx_ds2[1],
                                cosEdgeRotateCrossAngle*base_dx_ds3[2] - sinEdgeRotateCrossAngle*base_dx_ds2[2]
                            ]
                        mag = math.sqrt(rotatedOutward[0]*rotatedOutward[0] + rotatedOutward[1]*rotatedOutward[1] + rotatedOutward[2]*rotatedOutward[2])
                        unitRotatedOutward = [ rotatedOutward[0]/mag, rotatedOutward[1]/mag, rotatedOutward[2]/mag ]
                        scale3r = RVFreeWallThickness  # RVFreeWallThickness + edgeOffsetInner
                        if not onInside:
                            x[0] += unitRotatedOutward[0]*scale3r
                            x[1] += unitRotatedOutward[1]*scale3r
                            x[2] += unitRotatedOutward[2]*scale3r
                        dx_ds3 = [
                            unitRotatedOutward[0]*scale3r,
                            unitRotatedOutward[1]*scale3r,
                            unitRotatedOutward[2]*scale3r
                        ]
                        if onBottomEdge:
                            rscale2 = math.sqrt(dx_ds2[0]*dx_ds2[0] + dx_ds2[1]*dx_ds2[1] + dx_ds2[2]*dx_ds2[2])
                            tang = [
                                unitRotatedOutward[1]*dx_ds1[2] - unitRotatedOutward[2]*dx_ds1[1],
                                unitRotatedOutward[2]*dx_ds1[0] - unitRotatedOutward[0]*dx_ds1[2],
                                unitRotatedOutward[0]*dx_ds1[1] - unitRotatedOutward[1]*dx_ds1[0]
                            ]
                            rscale2 /= math.sqrt(tang[0]*tang[0] + tang[1]*tang[1] + tang[2]*tang[2])
                            dx_ds2 = [
                                tang[0]*rscale2,
                                tang[1]*rscale2,
                                tang[2]*rscale2
                            ]
                        if onSideEdge:
                            rscale1 = math.sqrt(dx_ds1[0]*dx_ds1[0] + dx_ds1[1]*dx_ds1[1] + dx_ds1[2]*dx_ds1[2])
                            tang = [
                                dx_ds2[1]*unitRotatedOutward[2] - dx_ds2[2]*unitRotatedOutward[1],
                                dx_ds2[2]*unitRotatedOutward[0] - dx_ds2[0]*unitRotatedOutward[2],
                                dx_ds2[0]*unitRotatedOutward[1] - dx_ds2[1]*unitRotatedOutward[0]
                            ]
                            rscale1 /= math.sqrt(tang[0]*tang[0] + tang[1]*tang[1] + tang[2]*tang[2])
                            dx_ds1 = [
                                tang[0]*rscale1,
                                tang[1]*rscale1,
                                tang[2]*rscale1
                            ]

                    if onInside and onBottomEdge and onSideEdge:
                        node = baseNode
                    else:
                        node = nodes.createNode(nodeIdentifier, nodetemplate)
                        nodeIdentifier += 1
                    cache.setNode(node)
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, x)
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, dx_ds1)
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, dx_ds2)
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, dx_ds3)
                    rv_nids.append(node.getIdentifier())

        # create RV elements and modify adjoining LV element fields
        elementIdentifier = elementsCountThroughLVWall*elementsCountUp*elementsCountAround + 1
        scalefactors5 = [ -1.0, sinCrossAngle, cosCrossAngle, sinCrossAngle, cosCrossAngle ]
        scalefactors9 = [ -1.0, 0.5, 0.25, 0.125, 0.75, sinCrossAngle, cosCrossAngle, sinCrossAngle, cosCrossAngle ]
        eow = elementsCountUp*elementsCountAround
        RVSeptumElementIdBase = eow*(elementsCountThroughLVWall - 1) + elementsCountBelowSeptum*elementsCountAround + 2

        # Add RV elements

        # RV LV joiner bottom elements: h-shape
        eftRVBottom = tricubichermite.createEftBasic()
        setEftScaleFactorIds(eftRVBottom, [1], [])
        # d/dxi2 is reversed on inside
        for ln in [1, 2]:
            n = ln - 1
            mapEftFunction1Node1Term(eftRVBottom, n*8 + 3, ln, Node.VALUE_LABEL_D_DS2, 1, [1])
        # d/dxi3 = -d/dS2 on LV side
        for ln in [1, 2, 5, 6]:
            n = ln - 1
            mapEftFunction1Node1Term(eftRVBottom, n*8 + 5, ln, Node.VALUE_LABEL_D_DS2, 1, [1])
        #print('eftRVBottom', eftRVBottom.validate())

        # RV LV joiner side 1 elements: h-shape
        eftRVSide1 = tricubichermite.createEftBasic()
        setEftScaleFactorIds(eftRVSide1, [1], [])
        # d/dxi1 is reversed on inside
        for ln in [1, 3]:
            n = ln - 1
            mapEftFunction1Node1Term(eftRVSide1, n*8 + 2, ln, Node.VALUE_LABEL_D_DS1, 1, [1])
        # d/dxi3 = -d/dS1 on LV side
        for ln in [1, 3, 5, 7]:
            n = ln - 1
            mapEftFunction1Node1Term(eftRVSide1, n*8 + 5, ln, Node.VALUE_LABEL_D_DS1, 1, [1])
        #print('eftRVSide1', eftRVSide1.validate())

        # RV LV joiner side 2 elements: h-shape
        eftRVSide2 = tricubichermite.createEftBasic()
        setEftScaleFactorIds(eftRVSide2, [1], [])
        # d/dxi1 is reversed on inside
        for ln in [2, 4]:
            n = ln - 1
            mapEftFunction1Node1Term(eftRVSide2, n*8 + 2, ln, Node.VALUE_LABEL_D_DS1, 1, [1])
        # d/dxi3 = d/dS1 on LV side
        for ln in [2, 4, 6, 8]:
            n = ln - 1
            mapEftFunction1Node1Term(eftRVSide2, n*8 + 5, ln, Node.VALUE_LABEL_D_DS1, 1, [])
        #print('eftRVSide2', eftRVSide2.validate())

        rv_nor = (elementsCountAcrossSeptum + 1)
        rv_now = (elementsCountUpRV + 1)*rv_nor

        for n2 in range(-1, elementsCountUpRV):

            for n1 in range(-1, elementsCountAcrossSeptum + 1):

                bni = lv_bni_base + n2*elementsCountAround + n1
                rv_bni = n2*rv_nor + n1
                eft1 = None

                if n2 == -1:
                    if n1 == -1:
                        # pinched to zero thickness on 3 sides
                        eft1 = tricubichermite.createEftNoCrossDerivatives()
                        remapEftNodeValueLabel(eft1, [ 1, 2, 3, 5, 6, 7 ], Node.VALUE_LABEL_D_DS3, [])
                        ln_map = [ 1, 2, 3, 4, 1, 2, 3, 5 ]
                        remapEftLocalNodes(eft1, 5, ln_map)
                        nodeIdentifiers = [ bni, bni + 1, bni + nor, bni + nor + 1, rv_nids[rv_bni + rv_nor + rv_now + 1] ]
                        #print('eft1 corner a', eft1.validate())
                    elif n1 == elementsCountAcrossSeptum:
                        # pinched to zero thickness on 3 sides
                        eft1 = tricubichermite.createEftNoCrossDerivatives()
                        remapEftNodeValueLabel(eft1, [ 1, 2, 4, 5, 6, 8 ], Node.VALUE_LABEL_D_DS3, [])
                        ln_map = [ 1, 2, 3, 4, 1, 2, 5, 4 ]
                        remapEftLocalNodes(eft1, 5, ln_map)
                        nodeIdentifiers = [ bni, bni + 1, bni + nor, bni + nor + 1, rv_nids[rv_bni + rv_nor + rv_now] ]
                        #print('eft1 corner b', eft1.validate())
                    else:
                        nodeIdentifiers = [
                            bni + nor, bni + nor + 1, rv_nids[rv_bni + rv_nor], rv_nids[rv_bni + rv_nor + 1],
                            bni, bni + 1, rv_nids[rv_bni + rv_nor + rv_now], rv_nids[rv_bni + rv_nor + 1 + rv_now]
                        ]
                        if n1 == 0:
                            eft1 = tricubichermite.createEftBasic()
                            # GRC to fix: uses repeated nodes instead of reducing to 7
                            #eft1.setNumberOfLocalNodes(7)
                            setEftScaleFactorIds(eft1, [1], [])
                            # d/dxi2 is zero on left side, reversed on inner right side
                            eft1.setFunctionNumberOfTerms(0*8 + 3, 0)
                            eft1.setFunctionNumberOfTerms(2*8 + 3, 0)
                            mapEftFunction1Node1Term(eft1, 1*8 + 3, 2, Node.VALUE_LABEL_D_DS2, 1, [1])
                            # d/dxi3 = -d/dS2 on LV side
                            for ln in [1, 2, 5, 6]:
                                n = ln - 1
                                mapEftFunction1Node1Term(eft1, n*8 + 5, ln, Node.VALUE_LABEL_D_DS2, 1, [1])
                        elif n1 == (elementsCountAcrossSeptum - 1):
                            eft1 = tricubichermite.createEftBasic()
                            # GRC to fix: uses repeated nodes instead of reducing to 7
                            #eft1.setNumberOfLocalNodes(7)
                            setEftScaleFactorIds(eft1, [1], [])
                            # d/dxi2 is zero on right side, reversed on inner left side
                            eft1.setFunctionNumberOfTerms(1*8 + 3, 0)
                            eft1.setFunctionNumberOfTerms(3*8 + 3, 0)
                            mapEftFunction1Node1Term(eft1, 0*8 + 3, 1, Node.VALUE_LABEL_D_DS2, 1, [1])
                            # d/dxi3 = -d/dS2 on LV side
                            for ln in [1, 2, 5, 6]:
                                n = ln - 1
                                mapEftFunction1Node1Term(eft1, n*8 + 5, ln, Node.VALUE_LABEL_D_DS2, 1, [1])
                        else:
                            eft1 = eftRVBottom
                else:
                    if n1 == -1:
                        nodeIdentifiers = [
                            bni + 1, rv_nids[rv_bni + 1], bni + nor + 1, rv_nids[rv_bni + rv_nor + 1],
                            bni, rv_nids[rv_bni + 1 + rv_now], bni + nor, rv_nids[rv_bni + rv_nor + 1 + rv_now]
                        ]
                        if n2 == 0:
                            eft1 = tricubichermite.createEftBasic()
                            # GRC to fix: uses repeated nodes instead of reducing to 7
                            #eft1.setNumberOfLocalNodes(7)
                            setEftScaleFactorIds(eft1, [1], [])
                            # d/dxi1 is zero on bottom side, reversed on inner top side
                            eft1.setFunctionNumberOfTerms(0*8 + 2, 0)
                            eft1.setFunctionNumberOfTerms(1*8 + 2, 0)
                            mapEftFunction1Node1Term(eft1, 2*8 + 2, 3, Node.VALUE_LABEL_D_DS1, 1, [1])
                            # d/dxi3 = -d/dS1 on LV side
                            for ln in [1, 3, 5, 7]:
                                n = ln - 1
                                mapEftFunction1Node1Term(eft1, n*8 + 5, ln, Node.VALUE_LABEL_D_DS1, 1, [1])
                            #print('eft1 next', eft1.validate())
                        else:
                            eft1 = eftRVSide1
                    elif n1 == elementsCountAcrossSeptum:
                        nodeIdentifiers = [
                            rv_nids[rv_bni], bni, rv_nids[rv_bni + rv_nor], bni + nor,
                            rv_nids[rv_bni + rv_now], bni + 1, rv_nids[rv_bni + rv_nor + rv_now], bni + nor + 1
                        ]
                        if n2 == 0:
                            eft1 = tricubichermite.createEftBasic()
                            # GRC to fix: uses repeated nodes instead of reducing to 7
                            #eft1.setNumberOfLocalNodes(7)
                            setEftScaleFactorIds(eft1, [1], [])
                            # d/dxi1 is zero on bottom side, reversed on inner top side
                            eft1.setFunctionNumberOfTerms(0*8 + 2, 0)
                            eft1.setFunctionNumberOfTerms(1*8 + 2, 0)
                            mapEftFunction1Node1Term(eft1, 3*8 + 2, 4, Node.VALUE_LABEL_D_DS1, 1, [1])
                            # d/dxi3 = d/dS1 on LV side
                            for ln in [2, 4, 6, 8]:
                                n = ln - 1
                                mapEftFunction1Node1Term(eft1, n*8 + 5, ln, Node.VALUE_LABEL_D_DS1, 1, [])
                            #print('eft1 next', eft1.validate())
                        else:
                            eft1 = eftRVSide2
                    else:
                        eft1 = eft
                        nodeIdentifiers = [
                            rv_nids[rv_bni], rv_nids[rv_bni + 1], rv_nids[rv_bni + rv_nor], rv_nids[rv_bni + rv_nor + 1],
                            rv_nids[rv_bni + rv_now], rv_nids[rv_bni + 1 + rv_now], rv_nids[rv_bni + rv_nor + rv_now], rv_nids[rv_bni + rv_nor + 1 + rv_now]
                        ]

                useElementtemplate = mesh.createElementtemplate()
                useElementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
                result1 = useElementtemplate.defineField(coordinates, -1, eft1)
                element = mesh.createElement(elementIdentifier, useElementtemplate)
                result2 = element.setNodesByIdentifier(eft1, nodeIdentifiers)
                if eft1.getNumberOfLocalScaleFactors() == 1:
                    element.setScaleFactors(eft1, [-1.0])
                #print('RV element create', elementIdentifier, result1, result2, nodeIdentifiers)
                elementIdentifier += 1

        fm.endChange()
Beispiel #16
0
    def generateBaseMesh(cls, region, options):
        """
        Generate the base tricubic Hermite mesh.
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: [] empty list of AnnotationGroup
        """
        elementsCount1 = options['Number of elements 1']
        elementsCount2 = options['Number of elements 2']
        elementsCount3 = options['Number of elements 3']
        useCrossDerivatives = options['Use cross derivatives']

        fm = region.getFieldmodule()
        fm.beginChange()
        coordinates = findOrCreateFieldCoordinates(fm)

        nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
        nodetemplate = nodes.createNodetemplate()
        nodetemplate.defineField(coordinates)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_VALUE, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS1, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS2, 1)
        if useCrossDerivatives:
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS1DS2,
                                                  1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS3, 1)
        if useCrossDerivatives:
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS1DS3,
                                                  1)
            nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                                  Node.VALUE_LABEL_D2_DS2DS3,
                                                  1)
            nodetemplate.setValueNumberOfVersions(
                coordinates, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1)

        mesh = fm.findMeshByDimension(3)

        tricubichermite = eftfactory_tricubichermite(mesh, useCrossDerivatives)
        eft = tricubichermite.createEftBasic()

        elementtemplate = mesh.createElementtemplate()
        elementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        result = elementtemplate.defineField(coordinates, -1, eft)

        cache = fm.createFieldcache()

        # create nodes
        nodeIdentifier = 1
        x = [0.0, 0.0, 0.0]
        dx_ds1 = [1.0 / elementsCount1, 0.0, 0.0]
        dx_ds2 = [0.0, 1.0 / elementsCount2, 0.0]
        dx_ds3 = [0.0, 0.0, 1.0 / elementsCount3]
        zero = [0.0, 0.0, 0.0]
        for n3 in range(elementsCount3 + 1):
            x[2] = n3 / elementsCount3
            for n2 in range(elementsCount2 + 1):
                x[1] = n2 / elementsCount2
                for n1 in range(elementsCount1 + 1):
                    x[0] = n1 / elementsCount1
                    node = nodes.createNode(nodeIdentifier, nodetemplate)
                    cache.setNode(node)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_VALUE, 1, x)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1,
                                                  dx_ds1)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1,
                                                  dx_ds2)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS3, 1,
                                                  dx_ds3)
                    if useCrossDerivatives:
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D2_DS1DS2, 1, zero)
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D2_DS1DS3, 1, zero)
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D2_DS2DS3, 1, zero)
                        coordinates.setNodeParameters(
                            cache, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1, zero)
                    nodeIdentifier = nodeIdentifier + 1

        # create elements
        elementIdentifier = 1
        no2 = (elementsCount1 + 1)
        no3 = (elementsCount2 + 1) * no2
        for e3 in range(elementsCount3):
            for e2 in range(elementsCount2):
                for e1 in range(elementsCount1):
                    element = mesh.createElement(elementIdentifier,
                                                 elementtemplate)
                    bni = e3 * no3 + e2 * no2 + e1 + 1
                    nodeIdentifiers = [
                        bni, bni + 1, bni + no2, bni + no2 + 1, bni + no3,
                        bni + no3 + 1, bni + no2 + no3, bni + no2 + no3 + 1
                    ]
                    result = element.setNodesByIdentifier(eft, nodeIdentifiers)
                    elementIdentifier = elementIdentifier + 1

        fm.endChange()
        return []
    def generateBaseMesh(cls, region, options):
        '''
        Generate the base tricubic Hermite mesh. See also generateMesh().
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: annotationGroups
        '''
        fm = region.getFieldmodule()
        coordinates = findOrCreateFieldCoordinates(fm)

        nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
        nodetemplate = nodes.createNodetemplate()
        nodetemplate.defineField(coordinates)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_VALUE, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS1, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS2, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS3, 1)

        mesh = fm.findMeshByDimension(3)
        cache = fm.createFieldcache()

        elementsCount1 = 2
        elementsCount2 = 4
        elementsCount3 = 4

        # Annotation fiducial point
        markerGroup = findOrCreateFieldGroup(fm, "marker")
        markerName = findOrCreateFieldStoredString(fm, name="marker_name")
        markerLocation = findOrCreateFieldStoredMeshLocation(
            fm, mesh, name="marker_location")

        nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
        markerPoints = findOrCreateFieldNodeGroup(markerGroup,
                                                  nodes).getNodesetGroup()
        markerTemplateInternal = nodes.createNodetemplate()
        markerTemplateInternal.defineField(markerName)
        markerTemplateInternal.defineField(markerLocation)

        # Create nodes
        nodeIdentifier = 1
        lNodeIds = []
        d1 = [0.5, 0.0, 0.0]
        d2 = [0.0, 0.5, 0.0]
        d3 = [0.0, 0.0, 1.0]
        for n3 in range(elementsCount3 + 1):
            lNodeIds.append([])
            for n2 in range(elementsCount2 + 1):
                lNodeIds[n3].append([])
                for n1 in range(elementsCount1 + 1):
                    lNodeIds[n3][n2].append([])
                    if n3 < elementsCount3:
                        if (n1 == 0) and ((n2 == 0) or (n2 == elementsCount2)):
                            continue
                    else:
                        if (n2 == 0) or (n2 == elementsCount2) or (n1 == 0):
                            continue
                    node = nodes.createNode(nodeIdentifier, nodetemplate)
                    cache.setNode(node)
                    x = [0.5 * (n1 - 1), 0.5 * (n2 - 1), 1.0 * n3]
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_VALUE, 1, x)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS1, 1,
                                                  d1)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS2, 1,
                                                  d2)
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D_DS3, 1,
                                                  d3)
                    lNodeIds[n3][n2][n1] = nodeIdentifier
                    nodeIdentifier += 1

        # Create elements
        mesh = fm.findMeshByDimension(3)
        eftfactory = eftfactory_tricubichermite(mesh, None)
        eftRegular = eftfactory.createEftBasic()

        elementtemplateRegular = mesh.createElementtemplate()
        elementtemplateRegular.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        elementtemplateRegular.defineField(coordinates, -1, eftRegular)

        elementtemplateCustom = mesh.createElementtemplate()
        elementtemplateCustom.setElementShapeType(Element.SHAPE_TYPE_CUBE)

        lungGroup = AnnotationGroup(region, get_lung_term("lung"))
        leftLungGroup = AnnotationGroup(region, get_lung_term("left lung"))
        rightLungGroup = AnnotationGroup(region, get_lung_term("right lung"))
        annotationGroups = [leftLungGroup, rightLungGroup, lungGroup]

        lungMeshGroup = lungGroup.getMeshGroup(mesh)
        leftLungMeshGroup = leftLungGroup.getMeshGroup(mesh)
        rightLungMeshGroup = rightLungGroup.getMeshGroup(mesh)

        eft1 = eftfactory.createEftWedgeCollapseXi1Quadrant([1, 5])
        eft2 = eftfactory.createEftWedgeCollapseXi1Quadrant([3, 7])
        eft3 = eftfactory.createEftWedgeCollapseXi2Quadrant([5, 6])
        eft4 = eftfactory.createEftWedgeCollapseXi2Quadrant([7, 8])
        eft5 = eftfactory.createEftWedgeCollapseXi1Quadrant([5, 7])
        eft6 = eftfactory.createEftTetrahedronCollapseXi1Xi2Quadrant(8, 2)
        eft7 = eftfactory.createEftTetrahedronCollapseXi1Xi2Quadrant(6, 3)

        elementIdentifier = 1
        for e3 in range(elementsCount3):
            for e2 in range(elementsCount2):
                for e1 in range(elementsCount1):
                    eft = eftRegular
                    nodeIdentifiers = [
                        lNodeIds[e3][e2][e1], lNodeIds[e3][e2][e1 + 1],
                        lNodeIds[e3][e2 + 1][e1], lNodeIds[e3][e2 + 1][e1 + 1],
                        lNodeIds[e3 + 1][e2][e1], lNodeIds[e3 + 1][e2][e1 + 1],
                        lNodeIds[e3 + 1][e2 + 1][e1],
                        lNodeIds[e3 + 1][e2 + 1][e1 + 1]
                    ]
                    scalefactors = None
                    if (e3 < elementsCount3 - 1):
                        if (e2 == 0) and (e1 == 0):
                            # Back wedge elements
                            nodeIdentifiers.pop(4)
                            nodeIdentifiers.pop(0)
                            eft = eft1
                            scalefactors = [-1.0]
                        elif (e2 == elementsCount2 - 1) and (e1 == 0):
                            # Front wedge elements
                            nodeIdentifiers.pop(6)
                            nodeIdentifiers.pop(2)
                            eft = eft2
                    else:
                        if (e2 == 0) and (e1 == 1):
                            # Top back wedge elements
                            nodeIdentifiers.pop(5)
                            nodeIdentifiers.pop(4)
                            eft = eft3
                        elif (e2 == elementsCount2 - 1) and (e1 == 1):
                            # Top front wedge elements
                            nodeIdentifiers.pop(7)
                            nodeIdentifiers.pop(6)
                            eft = eft4
                            scalefactors = [-1.0]
                        elif (e2 == 1) and (e1 == 0):
                            # Top middle back wedge element
                            nodeIdentifiers.pop(6)
                            nodeIdentifiers.pop(4)
                            eft = eft5
                        elif (e2 == 2) and (e1 == 0):
                            # Top middle front wedge element
                            nodeIdentifiers.pop(6)
                            nodeIdentifiers.pop(4)
                            eft = eft5
                        if (e2 == 0) and (e1 == 0):
                            # Top back tetrahedron element
                            nodeIdentifiers.pop(6)
                            nodeIdentifiers.pop(5)
                            nodeIdentifiers.pop(4)
                            nodeIdentifiers.pop(0)
                            eft = eft6
                            scalefactors = [-1.0]
                        if (e2 == elementsCount2 - 1) and (e1 == 0):
                            # Top front tetrahedron element
                            nodeIdentifiers.pop(7)
                            nodeIdentifiers.pop(6)
                            nodeIdentifiers.pop(4)
                            nodeIdentifiers.pop(2)
                            eft = eft7
                            scalefactors = [-1.0]

                    if eft is eftRegular:
                        element = mesh.createElement(elementIdentifier,
                                                     elementtemplateRegular)
                    else:
                        elementtemplateCustom.defineField(coordinates, -1, eft)
                        element = mesh.createElement(elementIdentifier,
                                                     elementtemplateCustom)
                    element.setNodesByIdentifier(eft, nodeIdentifiers)
                    if scalefactors:
                        element.setScaleFactors(eft, scalefactors)
                    elementIdentifier += 1
                    leftLungMeshGroup.addElement(element)
                    lungMeshGroup.addElement(element)

        # Apex annotation point
        idx = elementsCount1 * elementsCount2 * (
            elementsCount3 - 1) + elementsCount1 * (elementsCount2 // 2)
        element1 = mesh.findElementByIdentifier(idx)
        markerPoint = markerPoints.createNode(nodeIdentifier,
                                              markerTemplateInternal)
        nodeIdentifier += 1
        cache.setNode(markerPoint)
        markerName.assignString(cache, 'apex of left lung')
        markerLocation.assignMeshLocation(cache, element1, [1.0, 1.0, 1.0])

        return annotationGroups