Exemple #1
0
    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: list of AnnotationGroup
        """
        # set dependent outer diameter used in atria2
        options['LV outlet outer diameter'] = options[
            'LV outlet inner diameter'] + 2.0 * options[
                'LV outlet wall thickness']

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

        # generate heartventriclesbase2 model and put atria2 on it
        annotationGroups = MeshType_3d_heartventriclesbase2.generateBaseMesh(
            region, options)
        annotationGroups += MeshType_3d_heartatria2.generateBaseMesh(
            region, options)

        fm.endChange()
        return annotationGroups
    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: None
        """
        options['Diameter'] = 1.0
        radiusSphere = options['Diameter'] * 0.5
        radiusAnt = options['Anterior radius of curvature']
        radiusPos = options['Posterior radius of curvature']
        lensThickness = options['Axial thickness']
        sphereSphericalRadiusFraction = options[
            'Sphere spherical radius fraction']
        lensSphericalRadiusFraction = options['Lens spherical radius fraction']

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

        # generate solidsphere with unit diameter
        MeshType_3d_solidsphere1.generateBaseMesh(region, options)
        sphereCoordinates = zinc_utils.getOrCreateCoordinateField(fm)

        # Morph sphere surface to lens surface
        lensRC = getSphereToLensCoordinates(sphereCoordinates, radiusSphere,
                                            radiusAnt, radiusPos,
                                            lensThickness,
                                            sphereSphericalRadiusFraction,
                                            lensSphericalRadiusFraction)

        # Assign Field
        fieldassignment = sphereCoordinates.createFieldassignment(lensRC)
        result = fieldassignment.assign()

        fm.endChange()
    def generateBaseMesh(cls,
                         region,
                         options,
                         baseCentre=[0.0, 0.0, 0.0],
                         axisSide1=[0.0, -1.0, 0.0],
                         axisUp=[0.0, 0.0, 1.0]):
        """
        Generate the base bicubic-linear Hermite mesh. See also generateMesh().
        Optional extra parameters allow centre and axes to be set.
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :param baseCentre: Centre of valve on ventriculo-arterial junction.
        :param axisSide: Unit vector in first side direction where angle around starts.
        :param axisUp: Unit vector in outflow direction of valve.
        :return: list of AnnotationGroup
         """
        unitScale = options['Unit scale']
        outerHeight = unitScale * options['Outer height']
        innerDepth = unitScale * options['Inner depth']
        cuspHeight = unitScale * options['Cusp height']
        innerRadius = unitScale * 0.5 * options['Inner diameter']
        sinusRadialDisplacement = unitScale * options[
            'Sinus radial displacement']
        wallThickness = unitScale * options['Wall thickness']
        cuspThickness = unitScale * options['Cusp thickness']
        aorticNotPulmonary = options['Aortic not pulmonary']
        useCrossDerivatives = False

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

        if aorticNotPulmonary:
            arterialRootGroup = AnnotationGroup(region,
                                                'root of aorta',
                                                FMANumber=3740,
                                                lyphID='Lyph ID unknown')
            cuspGroups = [
                AnnotationGroup(region,
                                'posterior cusp of aortic valve',
                                FMANumber=7253,
                                lyphID='Lyph ID unknown'),
                AnnotationGroup(region,
                                'right cusp of aortic valve',
                                FMANumber=7252,
                                lyphID='Lyph ID unknown'),
                AnnotationGroup(region,
                                'left cusp of aortic valve',
                                FMANumber=7251,
                                lyphID='Lyph ID unknown')
            ]
        else:
            arterialRootGroup = AnnotationGroup(region,
                                                'root of pulmonary trunk',
                                                FMANumber=8612,
                                                lyphID='Lyph ID unknown')
            cuspGroups = [
                AnnotationGroup(region,
                                'right cusp of pulmonary valve',
                                FMANumber=7250,
                                lyphID='Lyph ID unknown'),
                AnnotationGroup(region,
                                'anterior cusp of pulmonary valve',
                                FMANumber=7249,
                                lyphID='Lyph ID unknown'),
                AnnotationGroup(region,
                                'left cusp of pulmonary valve',
                                FMANumber=7247,
                                lyphID='Lyph ID unknown')
            ]

        allGroups = [arterialRootGroup
                     ]  # groups that all elements in scaffold will go in
        annotationGroups = allGroups + cuspGroups

        # annotation fiducial points
        fiducialGroup = zinc_utils.getOrCreateGroupField(fm, 'fiducial')
        fiducialCoordinates = zinc_utils.getOrCreateCoordinateField(
            fm, 'fiducial_coordinates')
        fiducialLabel = zinc_utils.getOrCreateLabelField(fm, 'fiducial_label')
        #fiducialElementXi = zinc_utils.getOrCreateElementXiField(fm, 'fiducial_element_xi')

        datapoints = fm.findNodesetByFieldDomainType(
            Field.DOMAIN_TYPE_DATAPOINTS)
        fiducialPoints = zinc_utils.getOrCreateNodesetGroup(
            fiducialGroup, datapoints)
        datapointTemplateExternal = datapoints.createNodetemplate()
        datapointTemplateExternal.defineField(fiducialCoordinates)
        datapointTemplateExternal.defineField(fiducialLabel)

        #################
        # 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)
        # most nodes in this scaffold do not have a DS3 derivative
        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)
        # several only have a DS1 derivative
        nodetemplateLinearS2S3 = nodes.createNodetemplate()
        nodetemplateLinearS2S3.defineField(coordinates)
        nodetemplateLinearS2S3.setValueNumberOfVersions(
            coordinates, -1, Node.VALUE_LABEL_VALUE, 1)
        nodetemplateLinearS2S3.setValueNumberOfVersions(
            coordinates, -1, Node.VALUE_LABEL_D_DS1, 1)

        nodeIdentifier = max(1, zinc_utils.getMaximumNodeIdentifier(nodes) + 1)

        elementsCountAround = 6
        radiansPerElementAround = 2.0 * math.pi / elementsCountAround
        axisSide2 = vector.crossproduct3(axisUp, axisSide1)
        outerRadius = innerRadius + wallThickness
        cuspOuterLength2 = 0.5 * getApproximateEllipsePerimeter(
            innerRadius, cuspHeight)
        cuspOuterWallArcLength = cuspOuterLength2 * innerRadius / (
            innerRadius + cuspHeight)
        noduleOuterAxialArcLength = cuspOuterLength2 - cuspOuterWallArcLength
        noduleOuterRadialArcLength = innerRadius
        cuspOuterWalld1 = interp.interpolateLagrangeHermiteDerivative(
            [innerRadius, outerHeight + innerDepth - cuspHeight], [0.0, 0.0],
            [-innerRadius, 0.0], 0.0)

        sin60 = math.sin(math.pi / 3.0)
        cuspThicknessLowerFactor = 4.5  # GRC fudge factor
        cuspInnerLength2 = 0.5 * getApproximateEllipsePerimeter(
            innerRadius - cuspThickness / sin60,
            cuspHeight - cuspThicknessLowerFactor * cuspThickness)

        noduleInnerAxialArcLength = cuspInnerLength2 * (
            cuspHeight - cuspThicknessLowerFactor * cuspThickness) / (
                innerRadius - cuspThickness / sin60 + cuspHeight -
                cuspThicknessLowerFactor * cuspThickness)
        noduleInnerRadialArcLength = innerRadius - cuspThickness / math.tan(
            math.pi / 3.0)
        nMidCusp = 0 if aorticNotPulmonary else 1

        # lower points
        ix, id1 = createCirclePoints(
            [(baseCentre[c] - axisUp[c] * innerDepth) for c in range(3)],
            [axisSide1[c] * innerRadius for c in range(3)],
            [axisSide2[c] * innerRadius
             for c in range(3)], elementsCountAround)
        ox, od1 = getSemilunarValveSinusPoints(baseCentre,
                                               axisSide1,
                                               axisSide2,
                                               outerRadius,
                                               sinusRadialDisplacement,
                                               startMidCusp=aorticNotPulmonary)
        lowerx, lowerd1 = [ix, ox], [id1, od1]

        # upper points
        topCentre = [(baseCentre[c] + axisUp[c] * outerHeight)
                     for c in range(3)]
        # twice as many on inner:
        ix, id1 = createCirclePoints(
            topCentre, [axisSide1[c] * innerRadius for c in range(3)],
            [axisSide2[c] * innerRadius
             for c in range(3)], elementsCountAround * 2)
        # tweak inner points so elements attached to cusps are narrower
        cuspRadiansFactor = 0.25  # GRC fudge factor
        midDerivativeFactor = 1.0 + 0.5 * (1.0 - cuspRadiansFactor
                                           )  # GRC test compromise
        cuspAttachmentRadians = cuspRadiansFactor * radiansPerElementAround
        cuspAttachmentRadialDisplacement = wallThickness * 0.333  # GRC fudge factor
        cuspAttachmentRadius = innerRadius - cuspAttachmentRadialDisplacement
        for cusp in range(3):
            n1 = cusp * 2 - 1 + nMidCusp
            n2 = n1 * 2
            id1[n2 + 2] = [2.0 * d for d in id1[n2 + 2]]
            # side 1
            radiansAround = n1 * radiansPerElementAround + cuspAttachmentRadians
            rcosRadiansAround = cuspAttachmentRadius * math.cos(radiansAround)
            rsinRadiansAround = cuspAttachmentRadius * math.sin(radiansAround)
            ix[n2 + 1] = [(topCentre[c] + rcosRadiansAround * axisSide1[c] +
                           rsinRadiansAround * axisSide2[c]) for c in range(3)]
            id1[n2 + 1] = interp.interpolateLagrangeHermiteDerivative(
                ix[n2 + 1], ix[n2 + 2], id1[n2 + 2], 0.0)
            # side 2
            n1 = ((cusp + 1) * 2 - 1 + nMidCusp) % elementsCountAround
            n2 = n1 * 2
            radiansAround = n1 * radiansPerElementAround - cuspAttachmentRadians
            rcosRadiansAround = cuspAttachmentRadius * math.cos(radiansAround)
            rsinRadiansAround = cuspAttachmentRadius * math.sin(radiansAround)
            ix[n2 - 1] = [(topCentre[c] + rcosRadiansAround * axisSide1[c] +
                           rsinRadiansAround * axisSide2[c]) for c in range(3)]
            id1[n2 - 1] = interp.interpolateHermiteLagrangeDerivative(
                ix[n2 - 2], id1[n2 - 2], ix[n2 - 1], 1.0)
        ox, od1 = createCirclePoints(
            topCentre, [axisSide1[c] * outerRadius for c in range(3)],
            [axisSide2[c] * outerRadius
             for c in range(3)], elementsCountAround)
        upperx, upperd1 = [ix, ox], [id1, od1]

        # get lower and upper derivative 2
        zero = [0.0, 0.0, 0.0]
        upperd2factor = outerHeight
        upd2 = [d * upperd2factor for d in axisUp]
        lowerOuterd2 = interp.smoothCubicHermiteDerivativesLine(
            [lowerx[1][nMidCusp], upperx[1][nMidCusp]], [upd2, upd2],
            fixStartDirection=True,
            fixEndDerivative=True)[0]
        lowerd2factor = 2.0 * (outerHeight + innerDepth) - upperd2factor
        lowerInnerd2 = [d * lowerd2factor for d in axisUp]
        lowerd2 = [[lowerInnerd2] * elementsCountAround,
                   [lowerOuterd2] * elementsCountAround
                   ]  # some lowerd2[0] to be fitted below
        upperd2 = [[upd2] * (elementsCountAround * 2),
                   [upd2] * elementsCountAround]

        # get lower and upper derivative 1 or 2 pointing to/from cusps
        for n1 in range(elementsCountAround):
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            if (n1 % 2) == nMidCusp:
                lowerd2[0][n1] = [
                    -cuspOuterWallArcLength *
                    (cosRadiansAround * axisSide1[c] +
                     sinRadiansAround * axisSide2[c]) for c in range(3)
                ]
            else:
                upperd1[0][n1 * 2] = [
                    (cuspOuterWalld1[0] * (cosRadiansAround * axisSide1[c] +
                                           sinRadiansAround * axisSide2[c]) +
                     cuspOuterWalld1[1] * axisUp[c]) for c in range(3)
                ]

        # inner wall and mid sinus points; only every second one is used
        sinusDepth = innerDepth - cuspThicknessLowerFactor * cuspThickness  # GRC test
        sinusCentre = [(baseCentre[c] - sinusDepth * axisUp[c])
                       for c in range(3)]
        sinusx, sinusd1 = createCirclePoints(
            sinusCentre, [axisSide1[c] * innerRadius for c in range(3)],
            [axisSide2[c] * innerRadius
             for c in range(3)], elementsCountAround)
        # get sinusd2, parallel to lower inclined lines
        sd2 = interp.smoothCubicHermiteDerivativesLine(
            [[innerRadius, -sinusDepth], [innerRadius, outerHeight]],
            [[wallThickness + sinusRadialDisplacement, innerDepth],
             [0.0, upperd2factor]],
            fixStartDirection=True,
            fixEndDerivative=True)[0]
        sinusd2 = [None] * elementsCountAround
        for cusp in range(3):
            n1 = cusp * 2 + nMidCusp
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            sinusd2[n1] = [(sd2[0] * (cosRadiansAround * axisSide1[c] +
                                      sinRadiansAround * axisSide2[c]) +
                            sd2[1] * axisUp[c]) for c in range(3)]

        # get points on arc between mid sinus and upper cusp points
        arcx = []
        arcd1 = []
        scaled1 = 2.5  # GRC fudge factor
        for cusp in range(3):
            n1 = cusp * 2 + nMidCusp
            n1m = n1 - 1
            n1p = (n1 + 1) % elementsCountAround
            n2m = n1m * 2 + 1
            n2p = n1p * 2 - 1
            ax, ad1 = interp.sampleCubicHermiteCurves(
                [upperx[0][n2m], sinusx[n1]],
                [[-scaled1 * d for d in upperd2[0][n2m]],
                 [scaled1 * d for d in sinusd1[n1]]],
                elementsCountOut=2,
                addLengthStart=0.5 * vector.magnitude(upperd2[0][n2m]),
                lengthFractionStart=0.5,
                addLengthEnd=0.5 * vector.magnitude(sinusd1[n1]),
                lengthFractionEnd=0.5,
                arcLengthDerivatives=False)[0:2]
            arcx.append(ax[1])
            arcd1.append(ad1[1])
            ax, ad1 = interp.sampleCubicHermiteCurves(
                [
                    sinusx[n1],
                    upperx[0][n2p],
                ], [[scaled1 * d for d in sinusd1[n1]],
                    [scaled1 * d for d in upperd2[0][n2p]]],
                elementsCountOut=2,
                addLengthStart=0.5 * vector.magnitude(sinusd1[n1]),
                lengthFractionStart=0.5,
                addLengthEnd=0.5 * vector.magnitude(upperd2[0][n2p]),
                lengthFractionEnd=0.5,
                arcLengthDerivatives=False)[0:2]
            arcx.append(ax[1])
            arcd1.append(ad1[1])
        if nMidCusp == 0:
            arcx.append(arcx.pop(0))
            arcd1.append(arcd1.pop(0))

        # cusp nodule points
        noduleCentre = [(baseCentre[c] + axisUp[c] * (cuspHeight - innerDepth))
                        for c in range(3)]
        nodulex = [[], []]
        noduled1 = [[], []]
        noduled2 = [[], []]
        noduled3 = [[], []]
        cuspRadialThickness = cuspThickness / sin60
        for i in range(3):
            nodulex[0].append(noduleCentre)
            n1 = i * 2 + nMidCusp
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            nodulex[1].append([(noduleCentre[c] + cuspRadialThickness *
                                (cosRadiansAround * axisSide1[c] +
                                 sinRadiansAround * axisSide2[c]))
                               for c in range(3)])
            n1 = i * 2 - 1 + nMidCusp
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            noduled1[0].append([
                noduleOuterRadialArcLength * (cosRadiansAround * axisSide1[c] +
                                              sinRadiansAround * axisSide2[c])
                for c in range(3)
            ])
            noduled1[1].append(
                vector.setMagnitude(noduled1[0][i],
                                    noduleInnerRadialArcLength))
            n1 = i * 2 + 1 + nMidCusp
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            noduled2[0].append([
                noduleOuterRadialArcLength * (cosRadiansAround * axisSide1[c] +
                                              sinRadiansAround * axisSide2[c])
                for c in range(3)
            ])
            noduled2[1].append(
                vector.setMagnitude(noduled2[0][i],
                                    noduleInnerRadialArcLength))
            noduled3[0].append(
                [noduleOuterAxialArcLength * axisUp[c] for c in range(3)])
            noduled3[1].append(
                [noduleInnerAxialArcLength * axisUp[c] for c in range(3)])

        # Create nodes

        lowerNodeId = [[], []]
        for n3 in range(2):
            for n1 in range(elementsCountAround):
                node = nodes.createNode(nodeIdentifier, nodetemplateLinearS3)
                cache.setNode(node)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_VALUE, 1,
                                              lowerx[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS1, 1,
                                              lowerd1[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS2, 1,
                                              lowerd2[n3][n1])
                lowerNodeId[n3].append(nodeIdentifier)
                nodeIdentifier += 1

        sinusNodeId = []
        for n1 in range(elementsCountAround):
            if (n1 % 2) != nMidCusp:
                sinusNodeId.append(None)
                continue
            node = nodes.createNode(nodeIdentifier, nodetemplateLinearS3)
            cache.setNode(node)
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1,
                                          sinusx[n1])
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1,
                                          sinusd1[n1])
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1,
                                          sinusd2[n1])
            sinusNodeId.append(nodeIdentifier)
            nodeIdentifier += 1

        arcNodeId = []
        for n1 in range(elementsCountAround):
            node = nodes.createNode(nodeIdentifier, nodetemplateLinearS2S3)
            cache.setNode(node)
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1,
                                          arcx[n1])
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1,
                                          arcd1[n1])
            arcNodeId.append(nodeIdentifier)
            nodeIdentifier += 1

        noduleNodeId = [[], []]
        for n3 in range(2):
            for n1 in range(3):
                node = nodes.createNode(nodeIdentifier, nodetemplate)
                cache.setNode(node)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_VALUE, 1,
                                              nodulex[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS1, 1,
                                              noduled1[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS2, 1,
                                              noduled2[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS3, 1,
                                              noduled3[n3][n1])
                noduleNodeId[n3].append(nodeIdentifier)
                nodeIdentifier += 1

        upperNodeId = [[], []]
        for n3 in range(2):
            for n1 in range(len(upperx[n3])):
                node = nodes.createNode(nodeIdentifier, nodetemplateLinearS3)
                cache.setNode(node)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_VALUE, 1,
                                              upperx[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS1, 1,
                                              upperd1[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS2, 1,
                                              upperd2[n3][n1])
                upperNodeId[n3].append(nodeIdentifier)
                nodeIdentifier += 1

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

        mesh = fm.findMeshByDimension(3)

        allMeshGroups = [allGroup.getMeshGroup(mesh) for allGroup in allGroups]
        cuspMeshGroups = [
            cuspGroup.getMeshGroup(mesh) for cuspGroup in cuspGroups
        ]

        linearHermiteLinearBasis = fm.createElementbasis(
            3, Elementbasis.FUNCTION_TYPE_LINEAR_LAGRANGE)
        linearHermiteLinearBasis.setFunctionType(
            2, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE)

        hermiteLinearLinearBasis = fm.createElementbasis(
            3, Elementbasis.FUNCTION_TYPE_LINEAR_LAGRANGE)
        hermiteLinearLinearBasis.setFunctionType(
            1, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE)

        bicubichermitelinear = eftfactory_bicubichermitelinear(
            mesh, useCrossDerivatives)
        eftDefault = bicubichermitelinear.createEftNoCrossDerivatives()

        elementIdentifier = max(
            1,
            zinc_utils.getMaximumElementIdentifier(mesh) + 1)

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

        # wall elements
        for cusp in range(3):
            n1 = cusp * 2 - 1 + nMidCusp
            n2 = n1 * 2
            for e in range(6):
                eft1 = None
                scalefactors = None

                if (e == 0) or (e == 5):
                    # 6 node linear-hermite-linear collapsed wedge element expanding from zero width on outer wall of root, attaching to vertical part of cusp
                    eft1 = mesh.createElementfieldtemplate(
                        linearHermiteLinearBasis)
                    # switch mappings to use DS2 instead of default DS1
                    remapEftNodeValueLabel(eft1, [1, 2, 3, 4, 5, 6, 7, 8],
                                           Node.VALUE_LABEL_D_DS1,
                                           [(Node.VALUE_LABEL_D_DS2, [])])
                    if e == 0:
                        nids = [
                            lowerNodeId[0][n1], arcNodeId[n1],
                            upperNodeId[0][n2], upperNodeId[0][n2 + 1],
                            lowerNodeId[1][n1], upperNodeId[1][n1]
                        ]
                        setEftScaleFactorIds(eft1, [1], [])
                        scalefactors = [-1.0]
                        remapEftNodeValueLabel(eft1, [2],
                                               Node.VALUE_LABEL_D_DS2,
                                               [(Node.VALUE_LABEL_D_DS1, [1])])
                    else:
                        nids = [
                            arcNodeId[n1 + 1], lowerNodeId[0][n1 - 4],
                            upperNodeId[0][n2 + 3], upperNodeId[0][n2 - 8],
                            lowerNodeId[1][n1 - 4], upperNodeId[1][n1 - 4]
                        ]
                        remapEftNodeValueLabel(eft1, [1],
                                               Node.VALUE_LABEL_D_DS2,
                                               [(Node.VALUE_LABEL_D_DS1, [])])
                    ln_map = [1, 2, 3, 4, 5, 5, 6, 6]
                    remapEftLocalNodes(eft1, 6, ln_map)
                elif (e == 1) or (e == 4):
                    # 6 node hermite-linear-linear collapsed wedge element on lower wall
                    eft1 = mesh.createElementfieldtemplate(
                        hermiteLinearLinearBasis)
                    if e == 1:
                        nids = [
                            lowerNodeId[0][n1], lowerNodeId[0][n1 + 1],
                            arcNodeId[n1], sinusNodeId[n1 + 1],
                            lowerNodeId[1][n1], lowerNodeId[1][n1 + 1]
                        ]
                    else:
                        nids = [
                            lowerNodeId[0][n1 + 1], lowerNodeId[0][n1 - 4],
                            sinusNodeId[n1 + 1], arcNodeId[n1 + 1],
                            lowerNodeId[1][n1 + 1], lowerNodeId[1][n1 - 4]
                        ]
                    ln_map = [1, 2, 3, 4, 5, 6, 5, 6]
                    remapEftLocalNodes(eft1, 6, ln_map)
                else:
                    # 8 node elements with wedges on two sides
                    if e == 2:
                        eft1 = bicubichermitelinear.createEftNoCrossDerivatives(
                        )
                        setEftScaleFactorIds(eft1, [1], [])
                        scalefactors = [-1.0]
                        nids = [
                            arcNodeId[n1], sinusNodeId[n1 + 1],
                            upperNodeId[0][n2 + 1], upperNodeId[0][n2 + 2],
                            lowerNodeId[1][n1], lowerNodeId[1][n1 + 1],
                            upperNodeId[1][n1], upperNodeId[1][n1 + 1]
                        ]
                        remapEftNodeValueLabel(eft1, [1],
                                               Node.VALUE_LABEL_D_DS2,
                                               [(Node.VALUE_LABEL_D_DS1, [1])])
                    else:
                        eft1 = eftDefault
                        nids = [
                            sinusNodeId[n1 + 1], arcNodeId[n1 + 1],
                            upperNodeId[0][n2 + 2], upperNodeId[0][n2 + 3],
                            lowerNodeId[1][n1 + 1], lowerNodeId[1][n1 - 4],
                            upperNodeId[1][n1 + 1], upperNodeId[1][n1 - 4]
                        ]
                        remapEftNodeValueLabel(eft1, [2],
                                               Node.VALUE_LABEL_D_DS2,
                                               [(Node.VALUE_LABEL_D_DS1, [])])

                result = elementtemplate1.defineField(coordinates, -1, eft1)
                element = mesh.createElement(elementIdentifier,
                                             elementtemplate1)
                result2 = element.setNodesByIdentifier(eft1, nids)
                if scalefactors:
                    result3 = element.setScaleFactors(eft1, scalefactors)
                else:
                    result3 = 7
                #print('create arterial root wall', cusp, e, 'element',elementIdentifier, result, result2, result3, nids)
                elementIdentifier += 1

                for meshGroup in allMeshGroups:
                    meshGroup.addElement(element)

        # cusps (leaflets)
        for cusp in range(3):
            n1 = cusp * 2 - 1 + nMidCusp
            n2 = n1 * 2
            meshGroups = allMeshGroups + [cuspMeshGroups[cusp]]
            for e in range(2):
                eft1 = bicubichermitelinear.createEftNoCrossDerivatives()
                setEftScaleFactorIds(eft1, [1], [])
                scalefactors = [-1.0]

                if e == 0:
                    nids = [
                        lowerNodeId[0][n1], lowerNodeId[0][n1 + 1],
                        upperNodeId[0][n2], noduleNodeId[0][cusp],
                        arcNodeId[n1], sinusNodeId[n1 + 1],
                        upperNodeId[0][n2 + 1], noduleNodeId[1][cusp]
                    ]
                    remapEftNodeValueLabel(eft1, [4, 8],
                                           Node.VALUE_LABEL_D_DS1,
                                           [(Node.VALUE_LABEL_D_DS1, [1])])
                    remapEftNodeValueLabel(eft1, [4, 8],
                                           Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS3, [])])
                    remapEftNodeValueLabel(eft1, [5], Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS1, [1])])
                    remapEftNodeValueLabel(eft1, [6], Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS2, [1])])
                    remapEftNodeValueLabel(eft1, [7], Node.VALUE_LABEL_D_DS1,
                                           [(Node.VALUE_LABEL_D_DS1, [1])])
                else:
                    nids = [
                        lowerNodeId[0][n1 + 1], lowerNodeId[0][n1 - 4],
                        noduleNodeId[0][cusp], upperNodeId[0][n2 - 8],
                        sinusNodeId[n1 + 1], arcNodeId[n1 + 1],
                        noduleNodeId[1][cusp], upperNodeId[0][n2 + 3]
                    ]
                    remapEftNodeValueLabel(eft1, [3, 7],
                                           Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS3, [])])
                    remapEftNodeValueLabel(eft1, [3, 7],
                                           Node.VALUE_LABEL_D_DS1,
                                           [(Node.VALUE_LABEL_D_DS2, [])])
                    remapEftNodeValueLabel(eft1, [4, 8],
                                           Node.VALUE_LABEL_D_DS1,
                                           [(Node.VALUE_LABEL_D_DS1, [1])])
                    remapEftNodeValueLabel(eft1, [5], Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS2, [1])])
                    remapEftNodeValueLabel(eft1, [6], Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS1, [])])

                result = elementtemplate1.defineField(coordinates, -1, eft1)
                element = mesh.createElement(elementIdentifier,
                                             elementtemplate1)
                result2 = element.setNodesByIdentifier(eft1, nids)
                if scalefactors:
                    result3 = element.setScaleFactors(eft1, scalefactors)
                else:
                    result3 = 7
                #print('create semilunar cusp', cusp, e, 'element',elementIdentifier, result, result2, result3, nids)
                elementIdentifier += 1

                for meshGroup in meshGroups:
                    meshGroup.addElement(element)

        # create annotation points

        datapoint = fiducialPoints.createNode(-1, datapointTemplateExternal)
        cache.setNode(datapoint)
        fiducialCoordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_VALUE, 1,
                                              noduleCentre)
        fiducialLabel.assignString(
            cache, 'aortic valve ctr'
            if aorticNotPulmonary else 'pulmonary valve ctr')

        fm.endChange()
        return annotationGroups
    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()
Exemple #5
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
        """
        coordinateDimensions = options['Coordinate dimensions']
        length = options['Length']
        elementsCount = options['Number of elements']

        fm = region.getFieldmodule()
        fm.beginChange()
        coordinates = zinc_utils.getOrCreateCoordinateField(
            fm, componentsCount=coordinateDimensions)
        cache = fm.createFieldcache()

        #################
        # 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_D2_DS1DS2, 1)

        nodeIdentifier = 1
        x = [0.0, 0.0, 0.0]
        dx_ds1 = [length / elementsCount, 0.0, 0.0]
        dx_ds2 = [0.0, 1.0, 0.0]
        d2x_ds1ds2 = [0.0, 0.0, 0.0]
        for n in range(elementsCount + 1):
            x[0] = length * n / elementsCount
            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_D2_DS1DS2, 1,
                                          d2x_ds1ds2)
            nodeIdentifier = nodeIdentifier + 1

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

        mesh = fm.findMeshByDimension(1)
        cubicHermiteBasis = fm.createElementbasis(
            1, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE)
        eft = mesh.createElementfieldtemplate(cubicHermiteBasis)
        elementtemplate = mesh.createElementtemplate()
        elementtemplate.setElementShapeType(Element.SHAPE_TYPE_LINE)
        result = elementtemplate.defineField(coordinates, -1, eft)

        elementIdentifier = 1
        for e in range(elementsCount):
            element = mesh.createElement(elementIdentifier, elementtemplate)
            element.setNodesByIdentifier(eft, [e + 1, e + 2])
            elementIdentifier = elementIdentifier + 1

        fm.endChange()
    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
        """
        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 = 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()

        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()
Exemple #7
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']
        elementsCountAround = options['Number of elements around']
        useCrossDerivatives = options['Use cross derivatives']

        fm = region.getFieldmodule()
        fm.beginChange()
        coordinates = zinc_utils.getOrCreateCoordinateField(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 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)
        else:
            nodetemplate = nodetemplateApex

        mesh = fm.findMeshByDimension(2)
        bicubicHermiteBasis = fm.createElementbasis(
            2, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE)

        eft = mesh.createElementfieldtemplate(bicubicHermiteBasis)
        if not useCrossDerivatives:
            for n in range(4):
                eft.setFunctionNumberOfTerms(n * 4 + 4, 0)

        # Apex1: collapsed on xi2 = 0
        eftApex1 = mesh.createElementfieldtemplate(bicubicHermiteBasis)
        eftApex1.setNumberOfLocalNodes(3)
        eftApex1.setNumberOfLocalScaleFactors(7)
        # GRC: allow scale factor identifier for global -1.0 to be prescribed
        eftApex1.setScaleFactorType(
            1, Elementfieldtemplate.SCALE_FACTOR_TYPE_GLOBAL_GENERAL)
        eftApex1.setScaleFactorIdentifier(1, 1)
        for s in range(6):
            si = s + 2
            # 3 scale factors per node: cos(theta), sin(theta), arc angle radians
            sid = (s // 3) * 100 + (s %
                                    3) + 1  # add 100 for different 'version'
            eftApex1.setScaleFactorType(
                si, Elementfieldtemplate.SCALE_FACTOR_TYPE_NODE_GENERAL)
            eftApex1.setScaleFactorIdentifier(si, sid)
        # basis nodes 1, 2 -> local node 1
        ln = 1
        eftApex1.setTermNodeParameter(1, 1, ln, Node.VALUE_LABEL_VALUE, 1)
        # 0 terms = zero parameter for d/dxi1 basis
        eftApex1.setFunctionNumberOfTerms(2, 0)
        # 2 terms for d/dxi2 via general linear map:
        eftApex1.setFunctionNumberOfTerms(3, 2)
        eftApex1.setTermNodeParameter(3, 1, ln, Node.VALUE_LABEL_D_DS1, 1)
        eftApex1.setTermScaling(3, 1, [2])
        eftApex1.setTermNodeParameter(3, 2, ln, Node.VALUE_LABEL_D_DS2, 1)
        eftApex1.setTermScaling(3, 2, [3])
        # 2 terms for cross derivative 1 2 to correct circular apex: -sin(theta).phi, cos(theta).phi
        eftApex1.setFunctionNumberOfTerms(4, 2)
        eftApex1.setTermNodeParameter(4, 1, ln, Node.VALUE_LABEL_D_DS1, 1)
        eftApex1.setTermScaling(4, 1, [3, 4])
        eftApex1.setTermNodeParameter(4, 2, ln, Node.VALUE_LABEL_D_DS2, 1)
        eftApex1.setTermScaling(4, 2, [1, 2, 4])
        # basis node 2 -> local node 1
        eftApex1.setTermNodeParameter(5, 1, ln, Node.VALUE_LABEL_VALUE, 1)
        # 0 terms = zero parameter for d/dxi1 basis
        eftApex1.setFunctionNumberOfTerms(6, 0)
        # 2 terms for d/dxi2 via general linear map:
        eftApex1.setFunctionNumberOfTerms(7, 2)
        eftApex1.setTermNodeParameter(7, 1, ln, Node.VALUE_LABEL_D_DS1, 1)
        eftApex1.setTermScaling(7, 1, [5])
        eftApex1.setTermNodeParameter(7, 2, ln, Node.VALUE_LABEL_D_DS2, 1)
        eftApex1.setTermScaling(7, 2, [6])
        # 2 terms for cross derivative 1 2 to correct circular apex: -sin(theta).phi, cos(theta).phi
        eftApex1.setFunctionNumberOfTerms(8, 2)
        eftApex1.setTermNodeParameter(8, 1, ln, Node.VALUE_LABEL_D_DS1, 1)
        eftApex1.setTermScaling(8, 1, [6, 7])
        eftApex1.setTermNodeParameter(8, 2, ln, Node.VALUE_LABEL_D_DS2, 1)
        eftApex1.setTermScaling(8, 2, [1, 5, 7])

        # basis nodes 3, 4 -> regular local nodes 2, 3
        for bn in range(2, 4):
            fo = bn * 4
            ni = bn
            eftApex1.setTermNodeParameter(fo + 1, 1, ni,
                                          Node.VALUE_LABEL_VALUE, 1)
            eftApex1.setTermNodeParameter(fo + 2, 1, ni,
                                          Node.VALUE_LABEL_D_DS1, 1)
            eftApex1.setTermNodeParameter(fo + 3, 1, ni,
                                          Node.VALUE_LABEL_D_DS2, 1)
            if useCrossDerivatives:
                eftApex1.setTermNodeParameter(fo + 4, 1, ni,
                                              Node.VALUE_LABEL_D2_DS1DS2, 1)
            else:
                eftApex1.setFunctionNumberOfTerms(fo + 4, 0)

        # Apex2: collapsed on xi2 = 1
        eftApex2 = mesh.createElementfieldtemplate(bicubicHermiteBasis)
        eftApex2.setNumberOfLocalNodes(3)
        eftApex2.setNumberOfLocalScaleFactors(7)
        # GRC: allow scale factor identifier for global -1.0 to be prescribed
        eftApex2.setScaleFactorType(
            1, Elementfieldtemplate.SCALE_FACTOR_TYPE_GLOBAL_GENERAL)
        eftApex2.setScaleFactorIdentifier(1, 1)
        for s in range(6):
            si = s + 2
            # 3 scale factors per node: cos(theta), sin(theta), arc angle radians
            sid = (s // 3) * 100 + (s %
                                    3) + 1  # add 100 for different 'version'
            eftApex2.setScaleFactorType(
                si, Elementfieldtemplate.SCALE_FACTOR_TYPE_NODE_GENERAL)
            eftApex2.setScaleFactorIdentifier(si, sid)
        # basis nodes 1, 2 -> regular local nodes 1, 2 (for each layer)
        for bn in range(2):
            fo = bn * 4
            ni = bn + 1
            eftApex2.setTermNodeParameter(fo + 1, 1, ni,
                                          Node.VALUE_LABEL_VALUE, 1)
            eftApex2.setTermNodeParameter(fo + 2, 1, ni,
                                          Node.VALUE_LABEL_D_DS1, 1)
            eftApex2.setTermNodeParameter(fo + 3, 1, ni,
                                          Node.VALUE_LABEL_D_DS2, 1)
            if useCrossDerivatives:
                eftApex2.setTermNodeParameter(fo + 4, 1, ni,
                                              Node.VALUE_LABEL_D2_DS1DS2, 1)
            else:
                eftApex2.setFunctionNumberOfTerms(fo + 4, 0)

        # basis nodes 3, 4 -> local node 3
        ln = 3
        fo3 = 8
        eftApex2.setTermNodeParameter(fo3 + 1, 1, ln, Node.VALUE_LABEL_VALUE,
                                      1)
        # 0 terms = zero parameter for d/dxi1 basis
        eftApex2.setFunctionNumberOfTerms(fo3 + 2, 0)
        # 2 terms for d/dxi2 via general linear map:
        eftApex2.setFunctionNumberOfTerms(fo3 + 3, 2)
        eftApex2.setTermNodeParameter(fo3 + 3, 1, ln, Node.VALUE_LABEL_D_DS1,
                                      1)
        eftApex2.setTermScaling(fo3 + 3, 1, [2])
        eftApex2.setTermNodeParameter(fo3 + 3, 2, ln, Node.VALUE_LABEL_D_DS2,
                                      1)
        eftApex2.setTermScaling(fo3 + 3, 2, [3])
        # 2 terms for cross derivative 1 2 to correct circular apex: -sin(theta).phi, cos(theta).phi
        eftApex2.setFunctionNumberOfTerms(fo3 + 4, 2)
        eftApex2.setTermNodeParameter(fo3 + 4, 1, ln, Node.VALUE_LABEL_D_DS1,
                                      1)
        eftApex2.setTermScaling(fo3 + 4, 1, [1, 3, 4])
        eftApex2.setTermNodeParameter(fo3 + 4, 2, ln, Node.VALUE_LABEL_D_DS2,
                                      1)
        eftApex2.setTermScaling(fo3 + 4, 2, [2, 4])
        # basis node 4 -> local node 3
        eftApex2.setTermNodeParameter(fo3 + 5, 1, ln, Node.VALUE_LABEL_VALUE,
                                      1)
        # 0 terms = zero parameter for d/dxi1 basis
        eftApex2.setFunctionNumberOfTerms(fo3 + 6, 0)
        # 2 terms for d/dxi2 via general linear map:
        eftApex2.setFunctionNumberOfTerms(fo3 + 7, 2)
        eftApex2.setTermNodeParameter(fo3 + 7, 1, ln, Node.VALUE_LABEL_D_DS1,
                                      1)
        eftApex2.setTermScaling(fo3 + 7, 1, [5])
        eftApex2.setTermNodeParameter(fo3 + 7, 2, ln, Node.VALUE_LABEL_D_DS2,
                                      1)
        eftApex2.setTermScaling(fo3 + 7, 2, [6])
        # 2 terms for cross derivative 1 2 to correct circular apex: -sin(theta).phi, cos(theta).phi
        eftApex2.setFunctionNumberOfTerms(fo3 + 8, 2)
        eftApex2.setTermNodeParameter(fo3 + 8, 1, ln, Node.VALUE_LABEL_D_DS1,
                                      1)
        eftApex2.setTermScaling(fo3 + 8, 1, [1, 6, 7])
        eftApex2.setTermNodeParameter(fo3 + 8, 2, ln, Node.VALUE_LABEL_D_DS2,
                                      1)
        eftApex2.setTermScaling(fo3 + 8, 2, [5, 7])

        elementtemplate = mesh.createElementtemplate()
        elementtemplate.setElementShapeType(Element.SHAPE_TYPE_SQUARE)
        elementtemplate.defineField(coordinates, -1, eft)
        elementtemplateApex1 = mesh.createElementtemplate()
        elementtemplateApex1.setElementShapeType(Element.SHAPE_TYPE_SQUARE)
        elementtemplateApex1.defineField(coordinates, -1, eftApex1)
        elementtemplateApex2 = mesh.createElementtemplate()
        elementtemplateApex2.setElementShapeType(Element.SHAPE_TYPE_SQUARE)
        elementtemplateApex2.defineField(coordinates, -1, eftApex2)

        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]
        zero = [0.0, 0.0, 0.0]
        radius = 0.5

        # create apex1 node
        node = nodes.createNode(nodeIdentifier, nodetemplateApex)
        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,
                                      [0.0, radius * radiansPerElementUp, 0.0])
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1,
                                      [radius * radiansPerElementUp, 0.0, 0.0])
        nodeIdentifier = nodeIdentifier + 1

        # create regular rows between apexes
        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)
                x = [
                    radius * cosRadiansAround * sinRadiansUp,
                    radius * sinRadiansAround * sinRadiansUp,
                    -radius * cosRadiansUp
                ]
                dx_ds1 = [
                    radius * -sinRadiansAround * sinRadiansUp *
                    radiansPerElementAround, radius * cosRadiansAround *
                    sinRadiansUp * radiansPerElementAround, 0.0
                ]
                dx_ds2 = [
                    radius * cosRadiansAround * cosRadiansUp *
                    radiansPerElementUp, radius * sinRadiansAround *
                    cosRadiansUp * radiansPerElementUp,
                    radius * sinRadiansUp * radiansPerElementUp
                ]
                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 useCrossDerivatives:
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D2_DS1DS2,
                                                  1, zero)
                nodeIdentifier = nodeIdentifier + 1

        # create apex2 node
        node = nodes.createNode(nodeIdentifier, nodetemplateApex)
        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,
            [0.0, -radius * radiansPerElementUp, 0.0])
        coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1,
                                      [radius * radiansPerElementUp, 0.0, 0.0])
        nodeIdentifier = nodeIdentifier + 1

        # create elements
        elementIdentifier = 1
        # create Apex1 elements, editing eft scale factor identifiers around apex
        # scale factor identifiers follow convention of offsetting by 100 for each 'version'
        bni1 = 1
        for e1 in range(elementsCountAround):
            va = e1
            vb = (e1 + 1) % elementsCountAround
            eftApex1.setScaleFactorIdentifier(2, va * 100 + 1)
            eftApex1.setScaleFactorIdentifier(3, va * 100 + 2)
            eftApex1.setScaleFactorIdentifier(4, va * 100 + 3)
            eftApex1.setScaleFactorIdentifier(5, vb * 100 + 1)
            eftApex1.setScaleFactorIdentifier(6, vb * 100 + 2)
            eftApex1.setScaleFactorIdentifier(7, vb * 100 + 3)
            # redefine field in template for changes to eftApex1:
            elementtemplateApex1.defineField(coordinates, -1, eftApex1)
            element = mesh.createElement(elementIdentifier,
                                         elementtemplateApex1)
            bni2 = e1 + 2
            bni3 = (e1 + 1) % elementsCountAround + 2
            nodeIdentifiers = [bni1, bni2, bni3]
            element.setNodesByIdentifier(eftApex1, 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
            ]
            result = element.setScaleFactors(eftApex1, scalefactors)
            elementIdentifier = elementIdentifier + 1

        # create regular rows between apexes
        for e2 in range(elementsCountUp - 2):
            for e1 in range(elementsCountAround):
                element = mesh.createElement(elementIdentifier,
                                             elementtemplate)
                bni1 = e2 * elementsCountAround + e1 + 2
                bni2 = e2 * elementsCountAround + (e1 +
                                                   1) % elementsCountAround + 2
                nodeIdentifiers = [
                    bni1, bni2, bni1 + elementsCountAround,
                    bni2 + elementsCountAround
                ]
                result = element.setNodesByIdentifier(eft, nodeIdentifiers)
                elementIdentifier = elementIdentifier + 1

        # create Apex2 elements, editing eft scale factor identifiers around apex
        # scale factor identifiers follow convention of offsetting by 100 for each 'version'
        bni3 = 2 + (elementsCountUp - 1) * elementsCountAround
        for e1 in range(elementsCountAround):
            va = e1
            vb = (e1 + 1) % elementsCountAround
            eftApex2.setScaleFactorIdentifier(2, va * 100 + 1)
            eftApex2.setScaleFactorIdentifier(3, va * 100 + 2)
            eftApex2.setScaleFactorIdentifier(4, va * 100 + 3)
            eftApex2.setScaleFactorIdentifier(5, vb * 100 + 1)
            eftApex2.setScaleFactorIdentifier(6, vb * 100 + 2)
            eftApex2.setScaleFactorIdentifier(7, vb * 100 + 3)
            # redefine field in template for changes to eftApex2:
            elementtemplateApex1.defineField(coordinates, -1, eftApex2)
            element = mesh.createElement(elementIdentifier,
                                         elementtemplateApex1)
            bni1 = bni3 - elementsCountAround + e1
            bni2 = bni3 - elementsCountAround + (e1 + 1) % elementsCountAround
            nodeIdentifiers = [bni1, bni2, bni3]
            result = element.setNodesByIdentifier(eftApex2, 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
            ]
            result = element.setScaleFactors(eftApex2, scalefactors)
            elementIdentifier = elementIdentifier + 1

        fm.endChange()
    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: None
        """
        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 = zinc_utils.getOrCreateCoordinateField(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()
    def generateMesh(region, options):
        """
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: None
        """
        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 = 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()
        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()
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 __init__(self, sourceRegion, targetRegion, sourceAnnotationGroups=[]):
        '''
        Assumes targetRegion is empty.
        :param sourceAnnotationGroups: List of AnnotationGroup for source mesh in sourceRegion.
        A copy containing the refined elements is created by the MeshRefinement.
        '''
        self._sourceRegion = sourceRegion
        self._sourceFm = sourceRegion.getFieldmodule()
        self._sourceCache = self._sourceFm.createFieldcache()
        self._sourceCoordinates = zinc_utils.getOrCreateCoordinateField(
            self._sourceFm)
        # get range of source coordinates for octree range
        self._sourceFm.beginChange()
        sourceNodes = self._sourceFm.findNodesetByFieldDomainType(
            Field.DOMAIN_TYPE_NODES)
        minimumsField = self._sourceFm.createFieldNodesetMinimum(
            self._sourceCoordinates, sourceNodes)
        result, minimums = minimumsField.evaluateReal(self._sourceCache, 3)
        assert result == ZINC_OK, 'MeshRefinement failed to get minimum coordinates'
        maximumsField = self._sourceFm.createFieldNodesetMaximum(
            self._sourceCoordinates, sourceNodes)
        result, maximums = maximumsField.evaluateReal(self._sourceCache, 3)
        assert result == ZINC_OK, 'MeshRefinement failed to get maximum coordinates'
        xrange = [(maximums[i] - minimums[i]) for i in range(3)]
        edgeTolerance = 0.5 * (max(xrange))
        if edgeTolerance == 0.0:
            edgeTolerance = 1.0
        minimums = [(minimums[i] - edgeTolerance) for i in range(3)]
        maximums = [(maximums[i] + edgeTolerance) for i in range(3)]
        minimumsField = None
        maximumsField = None
        self._sourceMesh = self._sourceFm.findMeshByDimension(3)
        self._sourceElementiterator = self._sourceMesh.createElementiterator()
        self._octree = Octree(minimums, maximums)

        self._targetRegion = targetRegion
        self._targetFm = targetRegion.getFieldmodule()
        self._targetFm.beginChange()
        self._targetCache = self._targetFm.createFieldcache()
        self._targetCoordinates = zinc_utils.getOrCreateCoordinateField(
            self._targetFm)

        self._targetNodes = self._targetFm.findNodesetByFieldDomainType(
            Field.DOMAIN_TYPE_NODES)
        self._nodetemplate = self._targetNodes.createNodetemplate()
        self._nodetemplate.defineField(self._targetCoordinates)

        self._targetMesh = self._targetFm.findMeshByDimension(3)
        self._targetBasis = self._targetFm.createElementbasis(
            3, Elementbasis.FUNCTION_TYPE_LINEAR_LAGRANGE)
        self._targetEft = self._targetMesh.createElementfieldtemplate(
            self._targetBasis)
        self._targetElementtemplate = self._targetMesh.createElementtemplate()
        self._targetElementtemplate.setElementShapeType(
            Element.SHAPE_TYPE_CUBE)
        result = self._targetElementtemplate.defineField(
            self._targetCoordinates, -1, self._targetEft)

        self._nodeIdentifier = 1
        self._elementIdentifier = 1

        self._sourceAnnotationGroups = sourceAnnotationGroups
        self._annotationGroups = []
        self._sourceAndTargetMeshGroups = []
        for sourceAnnotationGroup in sourceAnnotationGroups:
            sourceMeshGroup = sourceAnnotationGroup.getMeshGroup(
                self._sourceMesh)
            targetAnnotationGroup = AnnotationGroup(self._targetRegion, \
                sourceAnnotationGroup.getName(), sourceAnnotationGroup.getFMANumber(), sourceAnnotationGroup.getLyphID())
            targetMeshGroup = targetAnnotationGroup.getMeshGroup(
                self._targetMesh)
            self._annotationGroups.append(targetAnnotationGroup)
            self._sourceAndTargetMeshGroups.append(
                (sourceMeshGroup, targetMeshGroup))
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 = zinc_utils.getOrCreateCoordinateField(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)
    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 = 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()

        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()
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 = 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)
    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 = zinc_utils.getOrCreateFlatCoordinateField(fm)
    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 = zinc_utils.getOrCreateTextureCoordinateField(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
Exemple #15
0
    def generateBaseMesh(region, options):
        """
        Generate the base tricubic Hermite or bicubic Hermite linear 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']
        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 = zinc_utils.getOrCreateCoordinateField(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()
Exemple #16
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
        """
        coordinateDimensions = options['Coordinate dimensions']
        elementsCount1 = options['Number of elements 1']
        elementsCount2 = options['Number of elements 2']
        useCrossDerivatives = options['Use cross derivatives']

        fm = region.getFieldmodule()
        fm.beginChange()
        coordinates = zinc_utils.getOrCreateCoordinateField(
            fm, componentsCount=coordinateDimensions)

        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)

        mesh = fm.findMeshByDimension(2)
        bicubicHermiteBasis = fm.createElementbasis(
            2, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE)
        eft = mesh.createElementfieldtemplate(bicubicHermiteBasis)
        if not useCrossDerivatives:
            for n in range(4):
                eft.setFunctionNumberOfTerms(n * 4 + 4, 0)
        elementtemplate = mesh.createElementtemplate()
        elementtemplate.setElementShapeType(Element.SHAPE_TYPE_SQUARE)
        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]
        zero = [0.0, 0.0, 0.0]
        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)
                if useCrossDerivatives:
                    coordinates.setNodeParameters(cache, -1,
                                                  Node.VALUE_LABEL_D2_DS1DS2,
                                                  1, zero)
                nodeIdentifier = nodeIdentifier + 1

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

        fm.endChange()
    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: list of AnnotationGroup
        """
        # set dependent outer diameter used in atria2
        options['Aorta outer plus diameter'] = options['LV outlet inner diameter'] + 2.0*options['LV outlet wall thickness']
        elementsCountAroundAtrialSeptum = options['Number of elements around atrial septum']
        elementsCountAroundLeftAtriumFreeWall = options['Number of elements around left atrium free wall']
        elementsCountAroundLeftAtrium = elementsCountAroundLeftAtriumFreeWall + elementsCountAroundAtrialSeptum
        elementsCountAroundRightAtriumFreeWall = options['Number of elements around right atrium free wall']
        elementsCountAroundRightAtrium = elementsCountAroundRightAtriumFreeWall + elementsCountAroundAtrialSeptum
        useCrossDerivatives = False

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

        # generate heartventriclesbase1 model and put atria1 on it
        annotationGroups = MeshType_3d_heartventriclesbase1.generateBaseMesh(region, options)
        annotationGroups += MeshType_3d_heartatria1.generateBaseMesh(region, options)
        lFibrousRingGroup = AnnotationGroup(region, 'left fibrous ring', FMANumber = 77124, lyphID = 'Lyph ID unknown')
        rFibrousRingGroup = AnnotationGroup(region, 'right fibrous ring', FMANumber = 77125, lyphID = 'Lyph ID unknown')
        annotationGroups += [ lFibrousRingGroup, rFibrousRingGroup ]

        # annotation fiducial points
        fiducialGroup = zinc_utils.getOrCreateGroupField(fm, 'fiducial')
        fiducialCoordinates = zinc_utils.getOrCreateCoordinateField(fm, 'fiducial_coordinates')
        fiducialLabel = zinc_utils.getOrCreateLabelField(fm, 'fiducial_label')
        fiducialElementXi = zinc_utils.getOrCreateElementXiField(fm, 'fiducial_element_xi')

        datapoints = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_DATAPOINTS)
        fiducialPoints = zinc_utils.getOrCreateNodesetGroup(fiducialGroup, datapoints)
        datapointTemplateInternal = datapoints.createNodetemplate()
        datapointTemplateInternal.defineField(fiducialCoordinates)
        datapointTemplateInternal.defineField(fiducialLabel)
        datapointTemplateInternal.defineField(fiducialElementXi)

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

        nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)

        # discover left and right fibrous ring nodes from ventricles and atria
        # because nodes are iterated in identifier order, the lowest and first are on the lv outlet cfb, right and left on lower outer layers
        # left fibrous ring
        lavNodeId = [ [ [], [] ], [ [], [] ] ]  # [n3][n2][n1]
        iter = lFibrousRingGroup.getNodesetGroup(nodes).createNodeiterator()
        # left fibrous ring, bottom row
        cfbNodeId = iter.next().getIdentifier()
        cfbLeftNodeId = iter.next().getIdentifier()
        for n1 in range(elementsCountAroundLeftAtrium):
            lavNodeId[0][0].append(iter.next().getIdentifier())
        lavNodeId[1][0].append(cfbNodeId)
        lavNodeId[1][0].append(cfbLeftNodeId)
        for n1 in range(elementsCountAroundLeftAtriumFreeWall - 1):
            lavNodeId[1][0].append(iter.next().getIdentifier())
        for n1 in range(elementsCountAroundAtrialSeptum - 1):
            lavNodeId[1][0].append(None)  # no outer node on interatrial septum
        # left fibrous ring, top row
        for n1 in range(elementsCountAroundLeftAtrium):
            lavNodeId[0][1].append(iter.next().getIdentifier())
        for n1 in range(elementsCountAroundLeftAtriumFreeWall + 1):
            lavNodeId[1][1].append(iter.next().getIdentifier())
        for n1 in range(elementsCountAroundAtrialSeptum - 1):
            lavNodeId[1][1].append(None)  # no outer node on interatrial septum
        # right fibrous ring
        ravNodeId = [ [ [], [] ], [ [], [] ] ]  # [n3][n2][n1]
        iter = rFibrousRingGroup.getNodesetGroup(nodes).createNodeiterator()
        cfbNodeId = iter.next().getIdentifier()
        cfbRightNodeId = iter.next().getIdentifier()
        # right fibrous ring, bottom row
        for n1 in range(elementsCountAroundRightAtrium):
            ravNodeId[0][0].append(iter.next().getIdentifier())
        for n1 in range(elementsCountAroundRightAtriumFreeWall - 1):
            ravNodeId[1][0].append(iter.next().getIdentifier())
        ravNodeId[1][0].append(cfbRightNodeId)
        ravNodeId[1][0].append(cfbNodeId)
        for n1 in range(elementsCountAroundAtrialSeptum - 1):
            ravNodeId[1][0].append(None)  # no outer node on interatrial septum
        # right fibrous ring, top row
        for n1 in range(elementsCountAroundRightAtrium):
            ravNodeId[0][1].append(iter.next().getIdentifier())
        cfbUpperNodeId = iter.next().getIdentifier()  # cfb from left will be first
        for n1 in range(elementsCountAroundRightAtriumFreeWall):
            ravNodeId[1][1].append(iter.next().getIdentifier())
        ravNodeId[1][1].append(cfbUpperNodeId)
        for n1 in range(elementsCountAroundAtrialSeptum - 1):
            ravNodeId[1][1].append(None)  # no outer node on interatrial septum

        #for n2 in range(2):
        #    print('n2', n2)
        #    print('lavNodeId[0]', lavNodeId[0][n2])
        #    print('lavNodeId[1]', lavNodeId[1][n2])
        #    print('ravNodeId[0]', ravNodeId[0][n2])
        #    print('ravNodeId[1]', ravNodeId[1][n2])

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

        mesh = fm.findMeshByDimension(3)

        lFibrousRingMeshGroup = lFibrousRingGroup.getMeshGroup(mesh)
        rFibrousRingMeshGroup = rFibrousRingGroup.getMeshGroup(mesh)

        elementIdentifier = startElementIdentifier = zinc_utils.getMaximumElementIdentifier(mesh) + 1

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

        # create fibrous ring elements

        bicubichermitelinear = eftfactory_bicubichermitelinear(mesh, useCrossDerivatives, linearAxis = 2, d_ds1 = Node.VALUE_LABEL_D_DS1, d_ds2 = Node.VALUE_LABEL_D_DS3)
        eftFibrousRing = bicubichermitelinear.createEftBasic()

        # left fibrous ring, starting at crux / collapsed posterior interatrial sulcus
        cruxElementId = None
        for e in range(-1, elementsCountAroundLeftAtriumFreeWall):
            eft1 = eftFibrousRing
            n1 = e
            nids = [
                lavNodeId[0][0][n1], lavNodeId[0][0][n1 + 1], lavNodeId[0][1][n1], lavNodeId[0][1][n1 + 1],
                lavNodeId[1][0][n1], lavNodeId[1][0][n1 + 1], lavNodeId[1][1][n1], lavNodeId[1][1][n1 + 1]]
            scalefactors = None
            meshGroups = [ lFibrousRingMeshGroup ]

            if e == -1:
                # interatrial groove straddles left and right atria, collapsed to 6 node wedge
                nids[0] = ravNodeId[0][0][elementsCountAroundRightAtriumFreeWall]
                nids[2] = ravNodeId[0][1][elementsCountAroundRightAtriumFreeWall]
                nids.pop(6)
                nids.pop(4)
                eft1 = bicubichermitelinear.createEftNoCrossDerivatives()
                setEftScaleFactorIds(eft1, [1], [])
                scalefactors = [ -1.0 ]
                remapEftNodeValueLabel(eft1, [ 1, 3 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [] ) ])
                remapEftNodeValueLabel(eft1, [ 2, 4 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ])
                remapEftNodeValueLabel(eft1, [ 5, 6, 7, 8 ], Node.VALUE_LABEL_D_DS1, [])
                # reverse d3 on cfb:
                remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [1]) ])
                remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [0] ), ( Node.VALUE_LABEL_D_DS3, [1]) ])
                remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ])
                remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ])
                ln_map = [ 1, 2, 3, 4, 5, 5, 6, 6 ]
                remapEftLocalNodes(eft1, 6, ln_map)
                meshGroups += [ rFibrousRingMeshGroup ]
            elif e == 0:
                # general linear map d3 adjacent to collapsed sulcus
                eft1 = bicubichermitelinear.createEftNoCrossDerivatives()
                setEftScaleFactorIds(eft1, [1], [])
                scalefactors = [ -1.0 ]
                # reverse d1, d3 on cfb, left cfb:
                scaleEftNodeValueLabels(eft1, [ 6 ], [ Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS3 ], [ 1 ])
                remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [1] ) ])
                remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [1]) ])
                remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ])
            elif e == 1:
                # reverse d1, d3 on left cfb:
                eft1 = bicubichermitelinear.createEftNoCrossDerivatives()
                setEftScaleFactorIds(eft1, [1], [])
                scalefactors = [ -1.0 ]
                remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ])
                remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS3, [1]) ])
            elif e == (elementsCountAroundLeftAtriumFreeWall - 1):
                # general linear map d3 adjacent to collapsed sulcus
                eft1 = bicubichermitelinear.createEftNoCrossDerivatives()
                setEftScaleFactorIds(eft1, [1], [])
                scalefactors = [ -1.0 ]
                remapEftNodeValueLabel(eft1, [ 6, 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ])

            result = elementtemplate1.defineField(coordinates, -1, eft1)
            element = mesh.createElement(elementIdentifier, elementtemplate1)
            result2 = element.setNodesByIdentifier(eft1, nids)
            if scalefactors:
                result3 = element.setScaleFactors(eft1, scalefactors)
            else:
                result3 = 7
            #print('create element fibrous ring left', elementIdentifier, result, result2, result3, nids)
            elementIdentifier += 1

            for meshGroup in meshGroups:
                meshGroup.addElement(element)

        # right fibrous ring, starting at crux / collapsed posterior interatrial sulcus
        for e in range(-1, elementsCountAroundRightAtriumFreeWall):
            eft1 = eftFibrousRing
            n1 = e
            nids = [
                ravNodeId[0][0][n1], ravNodeId[0][0][n1 + 1], ravNodeId[0][1][n1], ravNodeId[0][1][n1 + 1],
                ravNodeId[1][0][n1], ravNodeId[1][0][n1 + 1], ravNodeId[1][1][n1], ravNodeId[1][1][n1 + 1]]
            scalefactors = None
            meshGroups = [ rFibrousRingMeshGroup ]

            if e == -1:
                # interatrial groove straddles left and right atria, collapsed to 6 node wedge
                nids[0] = lavNodeId[0][0][elementsCountAroundLeftAtriumFreeWall]
                nids[2] = lavNodeId[0][1][elementsCountAroundLeftAtriumFreeWall]
                nids.pop(6)
                nids.pop(4)
                eft1 = bicubichermitelinear.createEftNoCrossDerivatives()
                setEftScaleFactorIds(eft1, [1], [])
                scalefactors = [ -1.0 ]
                remapEftNodeValueLabel(eft1, [ 1, 3 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [] ) ])
                remapEftNodeValueLabel(eft1, [ 2, 4 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ])
                remapEftNodeValueLabel(eft1, [ 5, 6, 7, 8 ], Node.VALUE_LABEL_D_DS1, [])
                remapEftNodeValueLabel(eft1, [ 5, 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ])
                remapEftNodeValueLabel(eft1, [ 6, 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ])
                ln_map = [ 1, 2, 3, 4, 5, 5, 6, 6 ]
                remapEftLocalNodes(eft1, 6, ln_map)
                meshGroups += [ lFibrousRingMeshGroup ]
                cruxElementId = elementIdentifier
            elif e == 0:
                # general linear map d3 adjacent to collapsed crux/posterior sulcus
                eft1 = bicubichermitelinear.createEftNoCrossDerivatives()
                setEftScaleFactorIds(eft1, [1], [])
                scalefactors = [ -1.0 ]
                remapEftNodeValueLabel(eft1, [ 5, 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ])
            elif e == (elementsCountAroundRightAtriumFreeWall - 2):
                # reverse d1, d3 on right cfb:
                eft1 = bicubichermitelinear.createEftNoCrossDerivatives()
                setEftScaleFactorIds(eft1, [1], [])
                scalefactors = [ -1.0 ]
                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_DS3, [ ( Node.VALUE_LABEL_D_DS3, [1]) ])
            elif e == (elementsCountAroundRightAtriumFreeWall - 1):
                # general linear map d3 adjacent to collapsed cfb/anterior sulcus
                eft1 = bicubichermitelinear.createEftNoCrossDerivatives()
                setEftScaleFactorIds(eft1, [1], [])
                scalefactors = [ -1.0 ]
                # reverse d1, d3 on right cfb, cfb:
                scaleEftNodeValueLabels(eft1, [ 5 ], [ 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] ) ])
                remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [1]) ])
                remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ])

            result = elementtemplate1.defineField(coordinates, -1, eft1)
            element = mesh.createElement(elementIdentifier, elementtemplate1)
            result2 = element.setNodesByIdentifier(eft1, nids)
            if scalefactors:
                result3 = element.setScaleFactors(eft1, scalefactors)
            else:
                result3 = 7
            #print('create element fibrous ring right', elementIdentifier, result, result2, result3, nids)
            elementIdentifier += 1

            for meshGroup in meshGroups:
                meshGroup.addElement(element)

        # fibrous ring septum:
        meshGroups = [ lFibrousRingMeshGroup, rFibrousRingMeshGroup ]
        for e in range(elementsCountAroundAtrialSeptum):
            eft1 = eftFibrousRing
            nlm = e - elementsCountAroundAtrialSeptum
            nlp = nlm + 1
            nrm = -e
            nrp = nrm - 1
            nids = [ lavNodeId[0][0][nlm], lavNodeId[0][0][nlp], lavNodeId[0][1][nlm], lavNodeId[0][1][nlp],
                     ravNodeId[0][0][nrm], ravNodeId[0][0][nrp], ravNodeId[0][1][nrm], ravNodeId[0][1][nrp] ]

            eft1 = bicubichermitelinear.createEftNoCrossDerivatives()
            setEftScaleFactorIds(eft1, [1], [])
            scalefactors = [ -1.0 ]
            if e == 0:
                # general linear map d3 adjacent to collapsed posterior interventricular sulcus
                scaleEftNodeValueLabels(eft1, [ 5, 6, 7, 8 ], [ Node.VALUE_LABEL_D_DS1 ], [ 1 ])
                scaleEftNodeValueLabels(eft1, [ 6, 8 ], [ Node.VALUE_LABEL_D_DS3 ], [ 1 ])
                remapEftNodeValueLabel(eft1, [ 1, 3 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [] ) ])
                remapEftNodeValueLabel(eft1, [ 5, 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ])
            elif e == (elementsCountAroundAtrialSeptum - 1):
                # general linear map d3 adjacent to cfb
                scaleEftNodeValueLabels(eft1, [ 5, 6, 7, 8 ], [ Node.VALUE_LABEL_D_DS1 ], [ 1 ])
                scaleEftNodeValueLabels(eft1, [ 5, 7 ], [ Node.VALUE_LABEL_D_DS3 ], [ 1 ])
                remapEftNodeValueLabel(eft1, [ 2, 4 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [] ) ])
                remapEftNodeValueLabel(eft1, [ 6, 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ])
            else:
                scaleEftNodeValueLabels(eft1, [ 5, 6, 7, 8 ], [ Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS3 ], [ 1 ])

            result = elementtemplate1.defineField(coordinates, -1, eft1)
            element = mesh.createElement(elementIdentifier, elementtemplate1)
            result2 = element.setNodesByIdentifier(eft1, nids)
            if scalefactors:
                result3 = element.setScaleFactors(eft1, scalefactors)
            else:
                result3 = 7
            #print('create element fibrous ring septum', elementIdentifier, result, result2, result3, nids)
            elementIdentifier += 1

            for meshGroup in meshGroups:
                meshGroup.addElement(element)

        # annotation fiducial points
        cruxElement = mesh.findElementByIdentifier(cruxElementId)
        cruxXi = [ 0.0, 0.5, 1.0 ]
        cache.setMeshLocation(cruxElement, cruxXi)
        result, cruxCoordinates = coordinates.evaluateReal(cache, 3)
        datapoint = fiducialPoints.createNode(-1, datapointTemplateInternal)
        cache.setNode(datapoint)
        fiducialCoordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, cruxCoordinates)
        fiducialLabel.assignString(cache, 'crux')
        fiducialElementXi.assignMeshLocation(cache, cruxElement, cruxXi)

        fm.endChange()
        return annotationGroups