Ejemplo n.º 1
0
    def createRegularRowCurves(self, rscx, rscd1, rscd3):
        """
        generate curves along regular rows using the mirror curve obtained from createMirrorCurve.
        :param rscx: Coordinates of the nodes on the middle curve.
        :param rscd1: d1 derivatives of the nodes on the middle curve.
        :param rscd3: d3 derivatives of the nodes on the middle curve.
        """
        btx = self.px
        btd1 = self.pd1
        btd2 = self.pd2
        btd3 = self.pd3

        elementsCountRim = 0
        for n2 in range(elementsCountRim + 2, self.elementsCountUp + 1):
            btx[n2], btd3[n2], pe, pxi, psf = sampleCubicHermiteCurves(
                [btx[n2][0], rscx[n2], btx[n2][-1]], [
                    vector.setMagnitude(btd3[n2][0], -1.0), rscd3[n2],
                    btd3[n2][-1]
                ],
                self.elementsCountAcrossMinor,
                lengthFractionStart=1,
                lengthFractionEnd=1,
                arcLengthDerivatives=True)
            btd1[n2] = interpolateSampleCubicHermite(
                [[-btd1[n2][0][c] for c in range(3)], rscd1[n2], btd1[n2][-1]],
                [[0.0, 0.0, 0.0]] * 3, pe, pxi, psf)[0]
            if n2 == self.elementsCountUp:
                for n1 in range(1, self.elementsCountAcrossMinor):
                    btd1[n2][n1] = vector.setMagnitude(
                        btd1[self.elementsCountUp][-1], 1.0)
            btd3[n2][0] = [-btd3[n2][0][c] for c in range(3)]
            btd1[n2][0] = [-btd1[n2][0][c] for c in range(3)]
Ejemplo n.º 2
0
 def resampleHermiteCurvePointsSmooth(self,
                                      nx,
                                      nd1,
                                      nd2,
                                      nd3,
                                      nProportions,
                                      derivativeMagnitudeStart=None,
                                      derivativeMagnitudeEnd=None):
     '''
     Call interp.sampleCubicHermiteCurvesSmooth on nx, nd1 and recalculate positions, nd2, nd3 for points.
     :return: nx[], nd1[], nd2[], nd3[], nProportions[]
     '''
     elementsCount = len(nx) - 1
     #print(nx, nd1, elementsCount, derivativeMagnitudeStart, derivativeMagnitudeEnd)
     nx, nd1 = interp.sampleCubicHermiteCurvesSmooth(
         nx, nd1, elementsCount, derivativeMagnitudeStart,
         derivativeMagnitudeEnd)[0:2]
     mag2 = vector.magnitude(nd2[0])
     if mag2 > 0.0:
         nd2[0] = vector.setMagnitude(nd2[0], vector.magnitude(nd1[0]))
     for n in range(1, elementsCount):
         p = self.findNearestPosition(
             nx[n], self.createPositionProportion(*nProportions[n]))
         nProportions[n] = self.getProportion(p)
         _, sd1, sd2 = self.evaluateCoordinates(p, derivatives=True)
         _, d2, d3 = calculate_surface_axes(sd1, sd2,
                                            vector.normalise(nd1[n]))
         nd2[n] = vector.setMagnitude(d2, vector.magnitude(nd1[n]))
         nd3[n] = d3
     mag2 = vector.magnitude(nd2[-1])
     if mag2 > 0.0:
         nd2[-1] = vector.setMagnitude(nd2[-1], vector.magnitude(nd1[-1]))
     return nx, nd1, nd2, nd3, nProportions
Ejemplo n.º 3
0
 def makeSideDerivativesNormal(cls, region, options, functionOptions, editGroupName):
     makeD2Normal = options['D2 derivatives'] and functionOptions['Make D2 normal']
     makeD3Normal = options['D3 derivatives'] and functionOptions['Make D3 normal']
     if not (makeD2Normal or makeD3Normal):
         return False, False
     valueLabels = [ Node.VALUE_LABEL_D_DS1 ]
     if options['D2 derivatives']:
         valueLabels.append(Node.VALUE_LABEL_D_DS2)
     if options['D3 derivatives']:
         valueLabels.append(Node.VALUE_LABEL_D_DS3)
     parameters = extractPathParametersFromRegion(region, valueLabels)
     d1 = parameters[0]
     modifyParameters = []
     modifyValueLabels = []
     if makeD2Normal:
         d2 = parameters[1]
         for c in range(len(d1)):
             td2 = vector.vectorRejection(d2[c], d1[c])
             d2[c] = vector.setMagnitude(td2, vector.magnitude(d2[c]))
         modifyParameters.append(d2)
         modifyValueLabels.append(Node.VALUE_LABEL_D_DS2)
     if makeD3Normal:
         d3 = parameters[-1]
         if options['D2 derivatives']:
             d2 = parameters[1]
             for c in range(len(d1)):
                 d3[c] = vector.setMagnitude(vector.crossproduct3(d1[c], d2[c]), vector.magnitude(d3[c]))
         else:
             for c in range(len(d1)):
                 td3 = vector.vectorRejection(d3[c], d1[c])
                 d3[c] = vector.setMagnitude(td3, vector.magnitude(d3[c]))
         modifyParameters.append(d3)
         modifyValueLabels.append(Node.VALUE_LABEL_D_DS3)
     setPathParameters(region, modifyValueLabels, modifyParameters, editGroupName)
     return False, True  # settings not changed, nodes changed
def getSemilunarValveSinusPoints(centre, axisSide1, axisSide2, radius, sinusRadialDisplacement, startMidCusp, elementsCountAround = 6):
    '''
    Get points around a circle of radius with every second point displaced radially.
    :return: x[], d1[] for 6 points around
    '''
    assert elementsCountAround == 6, 'getArterialRootLowerPoints.  Only supports 6 elements around'
    px = []
    pd1 = []
    # every second outer points is displaced by sinusRadialDisplacement to make sinus dilatation
    nMidCusp = 0 if startMidCusp else 1
    radiusPlus = radius + sinusRadialDisplacement
    radiansPerElementAround = 2.0*math.pi/elementsCountAround
    for n in range(elementsCountAround):
        radiansAround = n*radiansPerElementAround
        midCusp = (n % 2) == nMidCusp
        r = radiusPlus if midCusp else radius
        rcosRadiansAround = r*math.cos(radiansAround)
        rsinRadiansAround = r*math.sin(radiansAround)
        px.append([ (centre[c] + rcosRadiansAround*axisSide1[c] + rsinRadiansAround*axisSide2[c]) for c in range(3) ])
        pd1.append([ radiansPerElementAround*(-rsinRadiansAround*axisSide1[c] + rcosRadiansAround*axisSide2[c]) for c in range(3) ])
    # smooth to get derivative in sinus
    sd1 = interp.smoothCubicHermiteDerivativesLine(px[1 - nMidCusp:3 - nMidCusp], pd1[1 - nMidCusp:3 - nMidCusp], fixStartDerivative=True, fixEndDirection=True)
    magSinus = vector.magnitude(sd1[1])
    for n in range(nMidCusp, elementsCountAround, 2):
        pd1[n] = vector.setMagnitude(pd1[n], magSinus)

    return px, pd1
Ejemplo n.º 5
0
 def __init__(self,
              elementsCountAcrossMajor,
              elementsCountAcrossMinor,
              centre=None,
              alongAxis=None,
              majorAxis=None,
              minorRadius=None):
     """
     :param elementsCountAcrossMajor: Number of elements across major axis. Must be at least 2 + elementsCountRim for
      half and 4 + elementsCountRim for full cylinder.
     :param elementsCountAcrossMinor: Number of elements across minor axis.
     :param centre: Centre of the ellipse.
     :param alongAxis: The cylinder axis that the base is extruded along.
     :param majorAxis: The major axis of the base. Should be perpendicular to alongAxis
     :param minorRadius: The minor radius of the ellipse.
     """
     self._centre = centre
     self._alongAxis = alongAxis
     self._majorAxis = majorAxis
     self._minorRadius = minorRadius
     if alongAxis:
         self._minorAxis = vector.setMagnitude(
             vector.crossproduct3(alongAxis, majorAxis), minorRadius)
     self._elementsCountAcrossMinor = elementsCountAcrossMinor
     self._elementsCountAcrossMajor = elementsCountAcrossMajor
     self._majorRadius = vector.magnitude(majorAxis)
     self.px = None
     self.pd1 = None
     self.pd2 = None
     self.pd3 = None
Ejemplo n.º 6
0
def projectHermiteCurvesThroughWall(nx, nd1, nd2, n, wallThickness, loop = False):
    '''
    From Hermite curve nx, nd1 with cross direction nd2, project normal to wall
    by wall thickness to get coordinates, d1 affected by curvature etc.
    Assumes 3 components.
    :param n: Index into nx, nd1, nd2 of where to project.
    :param wallThickness: Use positive from in to out, negative from outside to in.
    :return: x, d1, d2, d3
    '''
    maxPointIndex = len(nx) - 1
    assert (0 <= n <= maxPointIndex), 'projectHermiteCurvesThroughWall.  Invalid index'
    unitNormal = vector.normalise(vector.crossproduct3(nd1[n], nd2[n]))
    x  = [ (nx[n][c] + wallThickness*unitNormal[c]) for c in range(3) ]
    # calculate inner d1 from curvature around
    curvature = 0.0
    count = 0
    if loop or (n > 0) and (nx[n - 1]):
        curvature += getCubicHermiteCurvature(nx[n - 1], nd1[n - 1], nx[n], nd1[n], unitNormal, 1.0)
        count += 1
    if loop or (n < maxPointIndex) and (nx[n - maxPointIndex]):
        curvature += getCubicHermiteCurvature(nx[n], nd1[n], nx[n - maxPointIndex], nd1[n - maxPointIndex], unitNormal, 0.0)
        count += 1
    curvature /= count
    factor = 1.0 - curvature*wallThickness
    d1 = [ factor*c for c in nd1[n] ]
    d2 = copy.deepcopy(nd2[n])  # magnitude can't be determined here
    d3 = vector.setMagnitude(unitNormal, math.fabs(wallThickness))
    return x, d1, d2, d3
Ejemplo n.º 7
0
def createEllipsoidPoints(centre, poleAxis, sideAxis, elementsCountAround,
                          elementsCountUp, height):
    '''
    Generate a set of points and derivatives for circle of revolution of an ellipse
    starting at pole poleAxis from centre.
    :param centre: Centre of full ellipsoid.
    :param poleAxis: Vector in direction of starting pole, magnitude is ellipse axis length.
    :param sideAxis: Vector normal to poleAxis, magnitude is ellipse side axis length.
    :param height: Height of arc of ellipsoid from starting pole along poleAxis.
    :return: Lists nx, nd1, nd2. Ordered fastest around, starting at pole. Suitable for passing to TrackSurface.
    '''
    nx = []
    nd1 = []
    nd2 = []
    magPoleAxis = vector.magnitude(poleAxis)
    magSideAxis = vector.magnitude(sideAxis)
    unitPoleAxis = vector.normalise(poleAxis)
    unitSideAxis1 = vector.normalise(sideAxis)
    unitSideAxis2 = vector.normalise(vector.crossproduct3(sideAxis, poleAxis))
    useHeight = min(max(0.0, height), 2.0 * magPoleAxis)
    totalRadiansUp = getEllipseRadiansToX(magPoleAxis,
                                          0.0,
                                          magPoleAxis - useHeight,
                                          initialTheta=0.5 * math.pi *
                                          useHeight / magPoleAxis)
    radiansUp = 0.0
    lengthUp = getEllipseArcLength(magPoleAxis, magSideAxis, radiansUp,
                                   totalRadiansUp)
    elementLengthUp = lengthUp / elementsCountUp
    radiansPerElementAround = 2.0 * math.pi / elementsCountAround
    for n2 in range(elementsCountUp + 1):
        cosRadiansUp = math.cos(radiansUp)
        sinRadiansUp = math.sin(radiansUp)
        radius = sinRadiansUp * magSideAxis
        d2r, d2z = vector.setMagnitude(
            [cosRadiansUp * magSideAxis, sinRadiansUp * magPoleAxis],
            elementLengthUp)
        cx = [(centre[c] + cosRadiansUp * poleAxis[c]) for c in range(3)]
        elementLengthAround = radius * radiansPerElementAround
        radiansAround = 0.0
        for n in range(elementsCountAround):
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            nx.append([(cx[c] + radius * (cosRadiansAround * unitSideAxis1[c] +
                                          sinRadiansAround * unitSideAxis2[c]))
                       for c in range(3)])
            nd1.append([
                (elementLengthAround * (-sinRadiansAround * unitSideAxis1[c] +
                                        cosRadiansAround * unitSideAxis2[c]))
                for c in range(3)
            ])
            nd2.append([(d2r * (cosRadiansAround * unitSideAxis1[c] +
                                sinRadiansAround * unitSideAxis2[c]) -
                         d2z * unitPoleAxis[c]) for c in range(3)])
            radiansAround += radiansPerElementAround
        radiansUp = updateEllipseAngleByArcLength(magPoleAxis, magSideAxis,
                                                  radiansUp, elementLengthUp)
    return nx, nd1, nd2
Ejemplo n.º 8
0
 def makeD2Normal(cls, region, options, editGroupName):
     if not options['D2 derivatives']:
         return
     d1, d2 = extractPathParametersFromRegion(
         region, [Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS2])
     for c in range(len(d1)):
         td2 = vector.vectorRejection(d2[c], d1[c])
         d2[c] = vector.setMagnitude(td2, vector.magnitude(d2[c]))
     setPathParameters(region, [Node.VALUE_LABEL_D_DS2], [d2],
                       editGroupName)
     return False, True  # settings not changed, nodes changed
Ejemplo n.º 9
0
def getDoubleCubicHermiteCurvesMidDerivative(ax, ad1, mx, bx, bd1):
    """
    Get derivative at centre of two cubic curves.
    :return: Derivative at mx to balance ax, ad1 with bx, bd1.
    """
    md1 = [ (bx[c] - ax[c]) for c in range(3) ]
    arcLengtha = computeCubicHermiteArcLength(ax, ad1, mx, md1, rescaleDerivatives = True)
    arcLengthb = computeCubicHermiteArcLength(mx, md1, bx, bd1, rescaleDerivatives = True)
    maga = vector.magnitude(ad1)
    magb = vector.magnitude(bd1)
    magm = arcLengtha + arcLengthb - 0.5*(maga + magb)
    return vector.setMagnitude(md1, magm)
Ejemplo n.º 10
0
def createEllipsePerimeter(centre, majorAxis, minorAxis, elementsCountAround,
                           height):
    """
    Generate a set of points and derivatives for an ellipse
    starting at pole majorAxis from centre.
    :param elementsCountAround: Number of elements around.
    :param centre: Centre of full ellipse.
    :param majorAxis: Vector in direction of starting major radius, magnitude is ellipse major radius.
    :param minorAxis: Vector normal to major axis, magnitude is ellipse minor axis length.
    :param height: Height of arc of ellipsoid from starting point along majorAxis.
    :return: Lists nx, nd1. Ordered fastest around, starting at major radius.
    """
    nx = []
    nd1 = []
    magMajorAxis = vector.magnitude(majorAxis)
    magMinorAxis = vector.magnitude(minorAxis)
    unitMajorAxis = vector.normalise(majorAxis)
    unitMinorAxis = vector.normalise(minorAxis)
    useHeight = min(max(0.0, height), 2.0 * magMajorAxis)

    totalRadians = math.acos((magMajorAxis - useHeight) / magMajorAxis)
    radians = 0.0

    arcLengthUp = geometry.getEllipseArcLength(magMajorAxis, magMinorAxis,
                                               radians, totalRadians,
                                               'integrate')
    elementsCountUp = elementsCountAround // 2
    elementArcLength = arcLengthUp / elementsCountUp

    radians = geometry.updateEllipseAngleByArcLength(magMajorAxis,
                                                     magMinorAxis,
                                                     radians,
                                                     -arcLengthUp,
                                                     method='Newton')
    for n1 in range(2 * elementsCountUp + 1):
        cosRadians = math.cos(radians)
        sinRadians = math.sin(radians)
        nx.append([
            (centre[c] + cosRadians * majorAxis[c] + sinRadians * minorAxis[c])
            for c in range(3)
        ])

        ndab = vector.setMagnitude(
            [-sinRadians * magMajorAxis, cosRadians * magMinorAxis],
            elementArcLength)
        nd1.append([(ndab[0] * unitMajorAxis[c] + ndab[1] * unitMinorAxis[c])
                    for c in range(3)])
        radians = geometry.updateEllipseAngleByArcLength(magMajorAxis,
                                                         magMinorAxis,
                                                         radians,
                                                         elementArcLength,
                                                         method='Newton')
    return nx, nd1
Ejemplo n.º 11
0
 def makeD3Normal(cls, region, options, editGroupName):
     if not options['D3 derivatives']:
         return
     if options['D2 derivatives']:
         d1, d2, d3 = extractPathParametersFromRegion(
             region, [
                 Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS2,
                 Node.VALUE_LABEL_D_DS3
             ])
         for c in range(len(d1)):
             d3[c] = vector.setMagnitude(vector.crossproduct3(d1[c], d2[c]),
                                         vector.magnitude(d3[c]))
     else:
         d1, d3 = extractPathParametersFromRegion(
             region, [Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS3])
         for c in range(len(d1)):
             td3 = vector.vectorRejection(d3[c], d1[c])
             d3[c] = vector.setMagnitude(td3, vector.magnitude(d3[c]))
     setPathParameters(region, [Node.VALUE_LABEL_D_DS3], [d3],
                       editGroupName)
     return False, True  # settings not changed, nodes changed
Ejemplo n.º 12
0
    def createMirrorCurve(self):
        """
        generate coordinates and derivatives for the mirror curve
        :return: Coordinates and derivatives for the mirror curve
        """
        btx = self.px
        btd1 = self.pd1
        btd2 = self.pd2
        btd3 = self.pd3

        n2a = self.elementsCountAcrossShell
        rcx = []
        tmdx = btx[n2a][self.elementsCountAcrossMinor // 2]
        tmdd3 = btd3[n2a][self.elementsCountAcrossMinor // 2]
        tmux = [
            0.5 * (btx[self.elementsCountUp][0][c] +
                   btx[self.elementsCountUp][self.elementsCountAcrossMinor][c])
            for c in range(3)
        ]
        rcx.append(tmdx)
        rcx.append(tmux)
        rcd3 = [vector.setMagnitude(tmdd3, -1), vector.setMagnitude(tmdd3, -1)]
        rscx, rscd1 = sampleCubicHermiteCurves(rcx,
                                               rcd3,
                                               self.elementsCountUp - n2a,
                                               arcLengthDerivatives=True)[0:2]

        # get d2, d3
        rscd2 = []
        rscd3 = []
        for n in range(len(rscx)):
            d3 = vector.normalise([
                btx[self.elementsCountUp][self.elementsCountAcrossMinor][c] -
                btx[self.elementsCountUp][0][c] for c in range(3)
            ])
            d2 = vector.normalise(vector.crossproduct3(d3, rscd1[n]))
            rscd2.append(d2)
            rscd3.append(d3)

        return rscx, rscd1, rscd2, rscd3
Ejemplo n.º 13
0
def computeNextRadius(radius, axis, ratio, progression):
    """
    calculate next radius based on the progression method. r_n+1=r_n*ratio for geometric. r_n+1=r_ratio for arithmetic.
    :param radius: radius (major or minor) along the central path.
    :param axis: major or minor axis along the central path.
    :param ratio: common ratio (common difference) for changing the next radius.
    :param progression: arithmetic or geometric.
    :return: next radius and axis.
    """
    if progression == ConeBaseProgression.GEOMETRIC_PROGRESSION:
        radius = radius * ratio
    elif progression == ConeBaseProgression.ARITHMETIC_PROGRESSION:
        radius += ratio
    axis = vector.setMagnitude(axis, radius)
    return radius, axis
Ejemplo n.º 14
0
 def __init__(self,
              elementsCountAcrossMajor,
              elementsCountAcrossMinor,
              elementsCountAcrossShell=0,
              elementsCountAcrossTransition=1,
              shellProportion=1.0,
              centre=None,
              alongAxis=None,
              majorAxis=None,
              minorRadius=None):
     """
     :param elementsCountAcrossMajor: Number of elements across major axis. Must be at least 2 + elementsCountRim for
      half and 4 + elementsCountRim for full cylinder.
     :param elementsCountAcrossMinor: Number of elements across minor axis.
     :param elementsCountAcrossShell: Number of elements across shell.
     :param elementsCountAcrossTransition: Number of elements between core boundary and inner square.
     :param shellProportion: Ratio of thickness of each layer in shell wrt thickness of each layer in core.
     :param centre: Centre of the ellipse.
     :param alongAxis: The cylinder axis that the base is extruded along.
     :param majorAxis: The major axis of the base. Should be perpendicular to alongAxis
     :param minorRadius: The minor radius of the ellipse.
     """
     self._centre = centre
     self._alongAxis = alongAxis
     self._majorAxis = majorAxis
     self._minorRadius = minorRadius
     if alongAxis:
         self._minorAxis = vector.setMagnitude(
             vector.crossproduct3(alongAxis, majorAxis), minorRadius)
     self._elementsCountAcrossMinor = elementsCountAcrossMinor
     self._elementsCountAcrossMajor = elementsCountAcrossMajor
     self._elementsCountAcrossShell = elementsCountAcrossShell
     self._elementsCountAcrossTransition = elementsCountAcrossTransition
     self._shellProportion = shellProportion
     self._majorRadius = vector.magnitude(majorAxis)
     self.px = None
     self.pd1 = None
     self.pd2 = None
     self.pd3 = None
Ejemplo n.º 15
0
    def setRimNodes(self, nx, nd1, nd2, nd3):
        """
        Set nodes around the ellipse perimeter in order needed for creating a shield mesh.
        """
        btx = self.px
        btd1 = self.pd1
        btd2 = self.pd2
        btd3 = self.pd3

        elementsCountRim = 0
        for n in range(self.elementsCountAround + 1):
            n1, n2 = self.__shield.convertRimIndex(n)
            btx[n2][n1] = nx[n]
            if n2 > elementsCountRim:  # regular rows
                btd1[n2][n1] = nd1[n]
                btd3[n2][n1] = nd3[n]
            if n2 >= 2:
                btd3[n2][n1] = vector.setMagnitude(
                    self.minorAxis, vector.dotproduct(nd3[n], self.minorAxis))
            else:  # around rim
                btd1[n2][n1] = nd1[n]
                btd3[n2][n1] = nd3[n]
            btd2[n2][n1] = nd2[n]
Ejemplo n.º 16
0
    def createRegularRowCurves(self, rscx, rscd1, rscd3):
        """
        generate curves along regular rows using the mirror curve obtained from createMirrorCurve.
        :param rscx: Coordinates of the nodes on the middle curve.
        :param rscd1: d1 derivatives of the nodes on the middle curve.
        :param rscd3: d3 derivatives of the nodes on the middle curve.
        """
        btx = self.px
        btd1 = self.pd1
        btd2 = self.pd2
        btd3 = self.pd3

        elementsCountRim = self.elementsCountAcrossRim
        n2a = self.elementsCountAcrossShell
        n2b = elementsCountRim
        n2d = n2b + 2
        n2m = self.elementsCountUp
        n1a = self.elementsCountAcrossShell
        n1b = elementsCountRim
        n1z = self.elementsCountAcrossMinor - self.elementsCountAcrossShell
        for n2 in range(n2d, n2m + 1):
            txm, td3m, pe, pxi, psf = sampleCubicHermiteCurves(
                [btx[n2][n1a], rscx[n2 - n2a], btx[n2][n1z]], [
                    vector.setMagnitude(btd3[n2][n1a], -1.0), rscd3[n2 - n2a],
                    btd3[n2][n1z]
                ],
                self.elementsCountAcrossMinor -
                2 * self.elementsCountAcrossShell,
                arcLengthDerivatives=True)
            td1m = interpolateSampleCubicHermite(
                [[-btd1[n2][n1a][c]
                  for c in range(3)], rscd1[n2 - n2a], btd1[n2][n1z]],
                [[0.0, 0.0, 0.0]] * 3, pe, pxi, psf)[0]

            tx = []
            td3 = []
            for n1 in range(self.elementsCountAcrossMinor + 1):
                if n1 <= n1a:
                    tx.append(btx[n2][n1])
                    td3.append([-btd3[n2][n1][c] for c in range(3)])
                elif (n1 > n1a) and (n1 < n1z):
                    tx.append(txm[n1 - n1a])
                    td3.append(td3m[n1 - n1a])
                else:
                    tx.append(btx[n2][n1])
                    td3.append((btd3[n2][n1]))

            td3 = smoothCubicHermiteDerivativesLine(tx,
                                                    td3,
                                                    fixStartDirection=True,
                                                    fixEndDirection=True)

            for n1 in range(self.elementsCountAcrossMinor + 1):
                if n1 <= n1a:
                    btd3[n2][n1] = [-td3[n1][c] for c in range(3)]
                elif (n1 > n1a) and (n1 < n1z):
                    btx[n2][n1] = tx[n1]
                    if n2 == n2m:
                        if n1 <= n1b:
                            btd1[n2][n1] = vector.setMagnitude(
                                btd1[n2m][-1], -1.0)
                        else:
                            btd1[n2][n1] = vector.setMagnitude(
                                btd1[n2m][-1], 1.0)
                    else:
                        if n1 <= n1b:
                            btd1[n2][n1] = [-d for d in td1m[n1 - n1a]]
                        else:
                            btd1[n2][n1] = td1m[n1 - n1a]
                    if n1 <= n1b:
                        btd3[n2][n1] = [-d for d in td3[n1]]
                    else:
                        btd3[n2][n1] = td3[n1]
                else:
                    btd3[n2][n1] = td3[n1]
Ejemplo n.º 17
0
    def createCylinderMesh3d(self, fieldModule, coordinates):
        """
        Create an extruded shape (ellipse/circle) mesh. Currently limited to ellipse or circle base with the alongAxis
        perpendicular to the base.
        :param fieldModule: Zinc fieldModule to create elements in.
        :param coordinates: Coordinate field to define.
        :return: Final values of nextNodeIdentifier, nextElementIdentifier.
        """
        assert (self._elementsCountAlong >
                0), 'createCylinderMesh3d:  Invalid number of along elements'
        assert (self._elementsCountAcrossMinor >
                3), 'createCylinderMesh3d: Invalid number of across elements'
        assert (self._elementsCountAcrossMinor % 2 == 0), 'createCylinderMesh3d: number of across elements' \
                                                          ' is not an even number'
        assert (self._elementsCountAcrossMajor >
                1), 'createCylinderMesh3d: Invalid number of up elements'
        assert (self._cylinderShape in [self._cylinderShape.CYLINDER_SHAPE_FULL,
                                        self._cylinderShape.CYLINDER_SHAPE_LOWER_HALF]), \
            'createCylinderMesh3d: Invalid cylinder mode.'

        nodes = fieldModule.findNodesetByFieldDomainType(
            Field.DOMAIN_TYPE_NODES)
        mesh = fieldModule.findMeshByDimension(3)

        elementsCountRim = self._elementsCountAcrossRim

        shieldMode = ShieldShape2D.SHIELD_SHAPE_FULL if self._cylinderShape is self._cylinderShape.CYLINDER_SHAPE_FULL \
            else ShieldShape2D.SHIELD_SHAPE_LOWER_HALF
        ellipseShape = EllipseShape.Ellipse_SHAPE_FULL \
            if self._cylinderShape is self._cylinderShape.CYLINDER_SHAPE_FULL else EllipseShape.Ellipse_SHAPE_LOWER_HALF
        self._shield = ShieldMesh2D(self._elementsCountAcrossMinor,
                                    self._elementsCountAcrossMajor,
                                    elementsCountRim,
                                    None,
                                    self._elementsCountAlong,
                                    shieldMode,
                                    shieldType=ShieldRimDerivativeMode.
                                    SHIELD_RIM_DERIVATIVE_MODE_AROUND)

        # generate ellipses mesh along cylinder axis
        n3Count = 0 if self._cylinderType == CylinderType.CYLINDER_STRAIGHT else self._elementsCountAlong
        self._ellipses = []
        for n3 in range(n3Count + 1):
            ellipse = Ellipse2D(self._centres[n3],
                                self._majorAxis[n3],
                                self._minorAxis[n3],
                                self._elementsCountAcrossMajor,
                                self._elementsCountAcrossMinor,
                                self._elementsCountAcrossShell,
                                self._elementsCountAcrossTransition,
                                self._shellProportion,
                                self._coreMajorRadii[n3],
                                self._coreMinorRadii[n3],
                                ellipseShape=ellipseShape)
            self._ellipses.append(ellipse)
            self.copyEllipsesNodesToShieldNodes(n3)

        for n3 in range(n3Count + 1):
            self.calculateD2Derivatives(n3, n3Count)

        if self._cylinderType == CylinderType.CYLINDER_TAPERED:
            self.smoothd2Derivatives()

        # The other ellipses for a straight cylinder.
        if self._cylinderType == CylinderType.CYLINDER_STRAIGHT:
            arcLengthAlong = vector.magnitude(
                self._base._alongAxis) / self._elementsCountAlong
            for n2 in range(self._elementsCountUp + 1):
                for n3 in range(self._elementsCountAlong + 1):
                    for n1 in range(self._elementsCountAcrossMinor + 1):
                        if self._shield.px[0][n2][n1]:
                            temx = [
                                self._shield.px[0][n2][n1][c] +
                                n3 * arcLengthAlong *
                                vector.normalise(self._base._alongAxis)[c]
                                for c in range(3)
                            ]
                            self._shield.px[n3][n2][n1] = temx
                            self._shield.pd1[n3][n2][n1] = self._shield.pd1[0][
                                n2][n1]
                            self._shield.pd2[n3][n2][n1] = self._shield.pd2[0][
                                n2][n1]
                            self._shield.pd3[n3][n2][n1] = self._shield.pd3[0][
                                n2][n1]

        self.generateNodes(nodes, fieldModule, coordinates)
        self.generateElements(mesh, fieldModule, coordinates)

        if self._end is None:
            self._end = CylinderEnds(
                self._elementsCountAcrossMajor, self._elementsCountAcrossMinor,
                self._elementsCountAcrossShell,
                self._elementsCountAcrossTransition, self._shellProportion,
                self._centres[-1], self._shield.pd2[-1][0][1],
                vector.setMagnitude(self._base._majorAxis,
                                    self._majorRadii[-1]),
                self._minorRadii[-1])
        self.setEndsNodes()
Ejemplo n.º 18
0
def smoothCubicHermiteDerivativesLine(
        nx,
        nd1,
        fixAllDirections=False,
        fixStartDerivative=False,
        fixEndDerivative=False,
        fixStartDirection=False,
        fixEndDirection=False,
        magnitudeScalingMode=DerivativeScalingMode.ARITHMETIC_MEAN,
        instrument=False):
    """
    Modifies derivatives nd1 to be smoothly varying and near arc length.
    Values are treated as being in a line.
    Assumes initial derivatives are zero or reasonable.
    Where directions are smoothed the weighted/harmonic mean is used.
    :param nx: List of coordinates of nodes along curves.
    :param nd1: List of derivatives of nodes along curves.
    :param fixAllDirections: Set to True to only smooth magnitudes, otherwise both direction and magnitude are adjusted.
    :param fixStartDerivative, fixEndDerivative: Set to True to fix derivative direction and magnitude at respective end.
    :param fixStartDirection, fixEndDirection: Set to True to fix direction at respective end.
    Redundant if fixAllDirections or respective fixStart/EndDerivative is True.
    :param magnitudeScalingMode: A value from enum DerivativeScalingMode specifying
    expression used to get derivative magnitude from adjacent arc lengths.
    :return: Modified nd1
    """
    nodesCount = len(nx)
    elementsCount = nodesCount - 1
    assert elementsCount > 0, 'smoothCubicHermiteDerivativesLine.  Too few nodes/elements'
    assert len(
        nd1
    ) == nodesCount, 'smoothCubicHermiteDerivativesLine.  Mismatched number of derivatives'
    arithmeticMeanMagnitude = magnitudeScalingMode is DerivativeScalingMode.ARITHMETIC_MEAN
    assert arithmeticMeanMagnitude or (magnitudeScalingMode is DerivativeScalingMode.HARMONIC_MEAN), \
        'smoothCubicHermiteDerivativesLine. Invalid magnitude scaling mode'
    md1 = copy.copy(nd1)
    componentsCount = len(nx[0])
    componentRange = range(componentsCount)
    if elementsCount == 1:
        # special cases for one element
        if not (fixStartDerivative or fixEndDerivative or fixStartDirection
                or fixEndDirection or fixAllDirections):
            # straight line
            delta = [(nx[1][c] - nx[0][c]) for c in componentRange]
            return [delta, copy.deepcopy(delta)]
        if fixAllDirections or (fixStartDirection and fixEndDirection):
            # fixed directions, equal magnitude
            arcLength = computeCubicHermiteArcLength(nx[0],
                                                     nd1[0],
                                                     nx[1],
                                                     nd1[1],
                                                     rescaleDerivatives=True)
            return [
                vector.setMagnitude(nd1[0], arcLength),
                vector.setMagnitude(nd1[1], arcLength)
            ]
    tol = 1.0E-6
    if instrument:
        print('iter 0', md1)
    for iter in range(100):
        lastmd1 = copy.copy(md1)
        arcLengths = [
            getCubicHermiteArcLength(nx[e], md1[e], nx[e + 1], md1[e + 1])
            for e in range(elementsCount)
        ]
        # start
        if not fixStartDerivative:
            if fixAllDirections or fixStartDirection:
                mag = 2.0 * arcLengths[0] - vector.magnitude(lastmd1[1])
                md1[0] = vector.setMagnitude(
                    nd1[0], mag) if (mag > 0.0) else [0.0, 0.0, 0.0]
            else:
                md1[0] = interpolateLagrangeHermiteDerivative(
                    nx[0], nx[1], lastmd1[1], 0.0)
        # middle
        for n in range(1, nodesCount - 1):
            nm = n - 1
            if not fixAllDirections:
                # get mean of directions from point n to points (n - 1) and (n + 1)
                np = n + 1
                dirm = [(nx[n][c] - nx[nm][c]) for c in componentRange]
                dirp = [(nx[np][c] - nx[n][c]) for c in componentRange]
                # mean weighted by fraction towards that end, equivalent to harmonic mean
                arcLengthmp = arcLengths[nm] + arcLengths[n]
                wm = arcLengths[n] / arcLengthmp
                wp = arcLengths[nm] / arcLengthmp
                md1[n] = [(wm * dirm[c] + wp * dirp[c])
                          for c in componentRange]
            if arithmeticMeanMagnitude:
                mag = 0.5 * (arcLengths[nm] + arcLengths[n])
            else:  # harmonicMeanMagnitude
                mag = 2.0 / (1.0 / arcLengths[nm] + 1.0 / arcLengths[n])
            md1[n] = vector.setMagnitude(md1[n], mag)
        # end
        if not fixEndDerivative:
            if fixAllDirections or fixEndDirection:
                mag = 2.0 * arcLengths[-1] - vector.magnitude(lastmd1[-2])
                md1[-1] = vector.setMagnitude(
                    nd1[-1], mag) if (mag > 0.0) else [0.0, 0.0, 0.0]
            else:
                md1[-1] = interpolateHermiteLagrangeDerivative(
                    nx[-2], lastmd1[-2], nx[-1], 1.0)
        if instrument:
            print('iter', iter + 1, md1)
        dtol = tol * sum(arcLengths) / len(arcLengths)
        for n in range(nodesCount):
            for c in componentRange:
                if math.fabs(md1[n][c] - lastmd1[n][c]) > dtol:
                    break
            else:
                continue
            break
        else:
            if instrument:
                print(
                    'smoothCubicHermiteDerivativesLine converged after iter:',
                    iter + 1)
            return md1

    cmax = 0.0
    for n in range(nodesCount):
        for c in componentRange:
            cmax = max(cmax, math.fabs(md1[n][c] - lastmd1[n][c]))
    closeness = cmax / dtol
    print('smoothCubicHermiteDerivativesLine max iters reached:', iter + 1,
          ', cmax = ', round(closeness, 2), 'x tolerance')
    return md1
Ejemplo n.º 19
0
def createFlatCoordinates(xiList, lengthAroundList, totalLengthAlong,
                          wallThickness, relativeThicknessList,
                          elementsCountAround, elementsCountAlong,
                          elementsCountThroughWall, transitElementList):
    """
    Calculates flat coordinates for a tube when it is opened into a flat preparation.
    :param xiList: List containing xi for each point around the outer surface of the tube.
    :param lengthAroundList: List of total arclength around the outer surface for each element along.
    :param totalLengthAlong: Total length along tube.
    :param wallThickness: Thickness of wall.
    :param elementsCountAround: Number of elements around tube.
    :param elementsCountAlong: Number of elements along tube.
    :param elementsCountThroughWall: Number of elements through wall.
    :param transitElementList: stores true if element around is a
    transition element between a big and small element.
    :return: coordinates and derivatives of flat coordinates field.
    """
    if relativeThicknessList:
        xi3 = 0.0
        xi3List = [0.0]
        for n3 in range(elementsCountThroughWall):
            xi3 += relativeThicknessList[n3]
            xi3List.append(xi3)
        relativeThicknessList.append(relativeThicknessList[-1])

    # Calculate flat coordinates and derivatives
    xFlatList = []
    d1FlatList = []
    d2FlatList = []
    for n2 in range(elementsCountAlong + 1):
        xiFace = xiList[n2]
        lengthAround = lengthAroundList[n2]
        d1List = []
        for n1 in range(len(xiFace)):
            d1 = (xiFace[n1] - xiFace[n1 - 1]) if n1 > 0 else (xiFace[n1 + 1] -
                                                               xiFace[n1])
            d1List.append(d1)

        # To modify derivative along transition elements
        for i in range(len(transitElementList)):
            if transitElementList[i]:
                d1List[i + 1] = d1List[i + 2]

        xPad = (lengthAroundList[0] - lengthAround) * 0.5
        for n3 in range(elementsCountThroughWall + 1):
            z = wallThickness * (xi3List[n3] if relativeThicknessList else
                                 1.0 / elementsCountThroughWall * n3)
            for n1 in range(elementsCountAround + 1):
                xFlat = [
                    xPad + xiFace[n1] * lengthAround,
                    totalLengthAlong / elementsCountAlong * n2, z
                ]
                d1Flat = [d1List[n1] * lengthAround, 0.0, 0.0]
                xFlatList.append(xFlat)
                d1FlatList.append(d1Flat)

    for n2 in range(elementsCountAlong):
        for n3 in range(elementsCountThroughWall + 1):
            for n1 in range(elementsCountAround + 1):
                nodeIdx = n2 * (elementsCountAround +
                                1) * (elementsCountThroughWall +
                                      1) + n3 * (elementsCountAround + 1) + n1
                nodeNextElementAlong = nodeIdx + (elementsCountAround + 1) * (
                    elementsCountThroughWall + 1)
                # print(nodeIdx + 1, nodeNextElementAlong + 1)
                v1 = xFlatList[nodeNextElementAlong]
                v2 = xFlatList[nodeIdx]
                d1 = d2 = [v1[i] - v2[i] for i in range(3)]
                arclength = interp.computeCubicHermiteArcLength(
                    v1, d1, v2, d2, True)
                d2Flat = vector.setMagnitude(d1, arclength)
                d2FlatList.append(d2Flat)
    d2FlatList = d2FlatList + d2FlatList[-(elementsCountAround + 1) *
                                         (elementsCountThroughWall + 1):]

    return xFlatList, d1FlatList, d2FlatList
Ejemplo n.º 20
0
def createArm(halfArmArcAngleRadians, elementLengths, elementLengthCentral,
              elementsCount, dipMultiplier, armCount, armIndex):
    """
    Create single arm unit.
    Base length of element is 1.
    Direction: anticlockwise.
    Minimum arm length is 2 elements.
    :param halfArmArcAngleRadians: angle arising from base node (rad)
    :param elementLengths: [Element length along arm, half Element width across arm, Element thickness]
    :param elementLengthCentral: Radial element length about central node.
    :param elementsCount: list of numbers of elements along arm length, across width and through thickness directions
    :param dipMultiplier: factor that wheel nodes protrude by, relative to unit length
    :param armCount: number of arms in body
    :param armIndex: 0-based
    :return: x: positions of nodes in arm
    :return: nodeIdentifier: number of last node in arm +1
    :return: xnodes_ds1: ds1 derivatives of nodes associated with x
    :return: xnodes_ds2: ds2 derivatives of nodes associated with y
    :return: rmVertexNodes: indices of nodes at arm vertices around central node (including central node)
    """

    elementsCount1, elementsCount2, elementsCount3 = elementsCount
    [elementLength, elementWidth, elementHeight] = elementLengths

    shorterArmEnd = True
    armAngle = 2 * halfArmArcAngleRadians * armIndex
    armAngleConst = 2 * halfArmArcAngleRadians

    xnodes_ds1 = []
    xnodes_ds2 = []
    dx_ds1 = [elementLength, 0.0, 0.0]
    dx_ds2 = [0.0, elementWidth, 0.0]

    nodes_per_layer = (elementsCount1 + 1) * (
        elementsCount2 + 1) - 2  # discount 2 armEnd corners
    x = []

    # nCentre and rmVertexNodes are in pythonic indexing
    nCentre = elementsCount1
    nCentre = [nCentre, nCentre + nodes_per_layer]
    if armIndex == 0:
        rmVertexNodes = []
    elif armIndex != armCount - 1:
        rmVertexNodes = nCentre + [0, nodes_per_layer]
    else:
        rmVertexNodes = nCentre + [
            0, nodes_per_layer, 2 * (elementsCount1 + 1) - 1, 2 *
            (elementsCount1 + 1) - 1 + nodes_per_layer
        ]
    dcent = [
        elementLengthCentral * math.cos(armAngleConst / 2),
        elementLengthCentral * math.sin(armAngleConst / 2), 0.0
    ]
    dvertex = [
        elementLengthCentral * dipMultiplier *
        math.cos(halfArmArcAngleRadians),
        elementLengthCentral * dipMultiplier * math.sin(halfArmArcAngleRadians)
    ]

    dipLength = 0.5 * (elementLengthCentral + elementWidth) * dipMultiplier
    dvertex = [
        dipLength * math.cos(halfArmArcAngleRadians),
        dipLength * math.sin(halfArmArcAngleRadians)
    ]
    dipMag = 2 * dipLength - elementLengthCentral

    nid = 0
    for e3 in range(elementsCount3 + 1):
        x3 = e3 * elementHeight
        for e2 in range(elementsCount2 + 1):
            for e1 in range(elementsCount1 + 1):
                # ignore if armEnd corner nodes
                if (e1 == elementsCount1) and ((e2 == 0) or
                                               (e2 == elementsCount2)):
                    pass
                else:
                    if e1 == 0 and e2 == 0:
                        x1 = dvertex[0]
                        x2 = -dvertex[1]
                    elif e1 == 0 and e2 == elementsCount2:
                        x1 = dvertex[0]
                        x2 = dvertex[1]
                    else:
                        if e1 == elementsCount1 and shorterArmEnd:
                            x1 = 0.5 * (elementLength + elementWidth
                                        ) + elementLength * (e1 - 1)
                        else:
                            x1 = elementLength * e1
                        x2 = (e2 - 1) * elementWidth
                    x.append([x1, x2, x3])

                    # DERIVATIVES
                    ds1 = dx_ds1.copy()
                    ds2 = dx_ds2.copy()
                    if e2 == 1 and e1 == 0:
                        ds2 = dcent
                        ds1 = [dcent[0], -dcent[1], dcent[2]]
                    elif (e1 == 0) and ((e2 == 0) or (e2 == elementsCount2)):
                        ds2 = [dcent[0], -dcent[1], dcent[2]
                               ] if (e2 == 0) else dcent
                        ds2 = setMagnitude(ds2, dipMag)
                        ds1 = rotateAboutZAxis(ds2, -math.pi / 2)
                    elif e1 == elementsCount1 and e2 == elementsCount2 - 1:  # armEnd
                        ds1 = [elementWidth, 0, 0]
                        ds2 = [0, 1 * (elementLength + elementWidth), 0]
                        if shorterArmEnd:
                            ds2 = [0, 0.5 * (elementLength + elementWidth), 0]
                    xnodes_ds1.append(ds1)
                    xnodes_ds2.append(ds2)
                    nid += 1

    # d1 derivatives at dips around central node
    nidDip = [
        0, elementsCount1 * 2 + 1, nodes_per_layer,
        elementsCount1 * 2 + 1 + nodes_per_layer
    ]
    for nid in nidDip:
        nx = [x[nid], x[nid + 1]]
        nd1 = [xnodes_ds1[nid], xnodes_ds1[nid + 1]]
        ds1Smooth = smoothCubicHermiteDerivativesLine(nx,
                                                      nd1,
                                                      fixStartDirection=True,
                                                      fixEndDerivative=True)
        xnodes_ds1[nid] = ds1Smooth[0]

    # rotate entire arm about origin by armAngle except for centre nodes
    tol = 1e-12
    for j, n in enumerate(x):
        xynew = rotateAboutZAxis(n, armAngle)
        [xnew, ynew] = [r for r in xynew][:2]
        if (abs(xnew) < tol):
            xnew = 0
        if (abs(ynew) < tol):
            ynew = 0
        x[j] = [xnew, ynew, x[j][2]]

        xnodes_ds1[j] = rotateAboutZAxis(xnodes_ds1[j], armAngle)
        xnodes_ds2[j] = rotateAboutZAxis(xnodes_ds2[j], armAngle)

    return (x, xnodes_ds1, xnodes_ds2, rmVertexNodes)
Ejemplo n.º 21
0
class MeshType_3d_wholebody1(Scaffold_base):
    """
Generates body coordinates using a solid cylinder of all cube elements,
 with variable numbers of elements in major, minor, shell and for each section of abdomen, thorax, neck and head.
    """
    cylinder1Settings = {
                'Coordinate dimensions': 3,
                'D2 derivatives': True,
                'D3 derivatives': True,
                'Length': 3.5,
                'Number of elements': 1
            }

    axis1 = [0, 0, 1]
    axis2 = [1, 0, 0]
    axis3 = [0, 1, 0]
    centralPathDefaultScaffoldPackages = {
        'Default': ScaffoldPackage(MeshType_1d_path1, {
            'scaffoldSettings': cylinder1Settings,
            'meshEdits': exnodeStringFromNodeValues(
                [Node.VALUE_LABEL_VALUE, Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS2, Node.VALUE_LABEL_D2_DS1DS2,
                 Node.VALUE_LABEL_D_DS3, Node.VALUE_LABEL_D2_DS1DS3], [
                    [[0.0, 0.0, 0.0], setMagnitude(axis1, cylinder1Settings['Length']), setMagnitude(axis2, 0.5), [0.0, 0.0, 0.0], setMagnitude(axis3, 0.5), [0.0, 0.0, 0.0]],
                    [setMagnitude(axis1, cylinder1Settings['Length']), setMagnitude(axis1, cylinder1Settings['Length']), setMagnitude(axis2, 0.5), [0.0, 0.0, 0.0], setMagnitude(axis3, 0.5), [0.0, 0.0, 0.0]]
                ])
        })
    }

    @staticmethod
    def getName():
        return '3D Whole Body 1'

    @staticmethod
    def getParameterSetNames():
        return [
            'Default',
            'Human Coarse',
            'Human Fine',
            'Rat Coarse',
            'Rat Fine'
        ]

    @classmethod
    def getDefaultOptions(cls, parameterSetName='Default'):
        if parameterSetName == 'Default':
            parameterSetName = 'Human Coarse'
        centralPathOption = cls.centralPathDefaultScaffoldPackages['Default']
        options = {}
        options['Base parameter set'] = parameterSetName
        options['Central path'] = copy.deepcopy(centralPathOption)
        options['Number of elements across major'] = 6
        options['Number of elements across minor'] = 6
        options['Number of elements across shell'] = 1
        options['Number of elements across transition'] = 1
        options['Number of elements in abdomen'] = 5
        options['Number of elements in thorax'] = 3
        options['Number of elements in neck'] = 1
        options['Number of elements in head'] = 2
        options['Shell thickness proportion'] = 0.2
        options['Discontinuity on the core boundary'] = True
        options['Lower half'] = False
        options['Use cross derivatives'] = False
        options['Refine'] = False
        options['Refine number of elements across major'] = 1
        options['Refine number of elements along'] = 1

        if 'Coarse' in parameterSetName:
            pass
        if 'Fine' in parameterSetName:
            options['Number of elements across major'] = 10
            options['Number of elements across minor'] = 10
            options['Number of elements across shell'] = 1
            options['Number of elements across transition'] = 1
            options['Number of elements in abdomen'] = 10
            options['Number of elements in thorax'] = 6
            options['Number of elements in neck'] = 2
            options['Number of elements in head'] = 4
        return options

    @staticmethod
    def getOrderedOptionNames():
        return [
            'Central path',
            'Number of elements across major',
            'Number of elements across minor',
            'Number of elements across shell',
            'Number of elements across transition',
            'Number of elements in abdomen',
            'Number of elements in thorax',
            'Number of elements in neck',
            'Number of elements in head',
            'Shell thickness proportion',
            'Discontinuity on the core boundary',
            'Refine',
            'Refine number of elements across major',
            'Refine number of elements along'
        ]

    @classmethod
    def getOptionValidScaffoldTypes(cls, optionName):
        if optionName == 'Central path':
            return [MeshType_1d_path1]
        return []

    @classmethod
    def getOptionScaffoldTypeParameterSetNames(cls, optionName, scaffoldType):
        if optionName == 'Central path':
            return list(cls.centralPathDefaultScaffoldPackages.keys())
        assert scaffoldType in cls.getOptionValidScaffoldTypes(optionName), \
            cls.__name__ + '.getOptionScaffoldTypeParameterSetNames.  ' + \
            'Invalid option \'' + optionName + '\' scaffold type ' + scaffoldType.getName()
        return scaffoldType.getParameterSetNames()

    @classmethod
    def getOptionScaffoldPackage(cls, optionName, scaffoldType, parameterSetName=None):
        '''
        :param parameterSetName:  Name of valid parameter set for option Scaffold, or None for default.
        :return: ScaffoldPackage.
        '''
        if parameterSetName:
            assert parameterSetName in cls.getOptionScaffoldTypeParameterSetNames(optionName, scaffoldType), \
                'Invalid parameter set ' + str(parameterSetName) + ' for scaffold ' + str(scaffoldType.getName()) + \
                ' in option ' + str(optionName) + ' of scaffold ' + cls.getName()
        if optionName == 'Central path':
            if not parameterSetName:
                parameterSetName = list(cls.centralPathDefaultScaffoldPackages.keys())[0]
            return copy.deepcopy(cls.centralPathDefaultScaffoldPackages[parameterSetName])
        assert False, cls.__name__ + '.getOptionScaffoldPackage:  Option ' + optionName + ' is not a scaffold'

    @classmethod
    def checkOptions(cls, options):
        if not options['Central path'].getScaffoldType() in cls.getOptionValidScaffoldTypes('Central path'):
            options['Central path'] = cls.getOptionScaffoldPackage('Central path', MeshType_1d_path1)
        dependentChanges = False

        if options['Number of elements across major'] < 4:
            options['Number of elements across major'] = 4
        if options['Number of elements across major'] % 2:
            options['Number of elements across major'] += 1

        if options['Number of elements across minor'] != options['Number of elements across major']:
            options['Number of elements across minor'] = options['Number of elements across major']
            dependentChanges = True

        if options['Number of elements across transition'] < 1:
            options['Number of elements across transition'] = 1

        Rcrit = min(options['Number of elements across major']-4, options['Number of elements across minor']-4)//2
        if options['Number of elements across shell'] + options['Number of elements across transition'] - 1 > Rcrit:
            dependentChanges = True
            options['Number of elements across shell'] = Rcrit
            options['Number of elements across transition'] = 1

        if options['Shell thickness proportion'] < 0.07 or options['Shell thickness proportion'] > 0.7:
            options['Shell thickness proportion'] = 2*options['Number of elements across shell']/options['Number of elements across major']

        if options['Number of elements in abdomen'] < 1:
            options['Number of elements in abdomen'] = 1
        if options['Number of elements in head'] < 1:
            options['Number of elements in head'] = 1
        if options['Number of elements in neck'] < 1:
            options['Number of elements in neck'] = 1
        if options['Number of elements in thorax'] < 1:
            options['Number of elements in thorax'] = 1

        return dependentChanges

    @staticmethod
    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: List of AnnotationGroup
        """

        baseParameterSetName = options['Base parameter set']
        isHuman = 'Human' in baseParameterSetName
        isRat = 'Rat' in baseParameterSetName

        centralPath = options['Central path']
        full = not options['Lower half']
        elementsCountAcrossMajor = options['Number of elements across major']
        if not full:
            elementsCountAcrossMajor //= 2
        elementsCountAcrossMinor = options['Number of elements across minor']
        elementsCountAcrossShell = options['Number of elements across shell']
        elementsCountAcrossTransition = options['Number of elements across transition']
        elementsCountAlongAbdomen = options['Number of elements in abdomen']
        elementsCountAlongHead = options['Number of elements in head']
        elementsCountAlongNeck = options['Number of elements in neck']
        elementsCountAlongThorax = options['Number of elements in thorax']
        shellRadiusProportion = options['Shell thickness proportion']
        shellProportion = 1/(1/shellRadiusProportion-1)*(elementsCountAcrossMajor/2/elementsCountAcrossShell - 1)
        discontinuity = options['Discontinuity on the core boundary']
        useCrossDerivatives = options['Use cross derivatives']

        elementsCountAlong = elementsCountAlongAbdomen + elementsCountAlongThorax + elementsCountAlongNeck + elementsCountAlongHead

        fieldmodule = region.getFieldmodule()
        coordinates = findOrCreateFieldCoordinates(fieldmodule)
        mesh = fieldmodule.findMeshByDimension(3)

        bodyGroup = AnnotationGroup(region, get_body_term("body"))
        coreGroup = AnnotationGroup(region, get_body_term("core"))
        non_coreGroup = AnnotationGroup(region, get_body_term("non core"))
        abdomenGroup = AnnotationGroup(region, get_body_term("abdomen"))
        thoraxGroup = AnnotationGroup(region, get_body_term("thorax"))
        neckGroup = AnnotationGroup(region, get_body_term("neck core"))
        headGroup = AnnotationGroup(region, get_body_term("head core"))
        annotationGroups = [bodyGroup, coreGroup, non_coreGroup, abdomenGroup, thoraxGroup, neckGroup, headGroup]

        cylinderCentralPath = CylinderCentralPath(region, centralPath, elementsCountAlong)

        cylinderShape = CylinderShape.CYLINDER_SHAPE_FULL

        base = CylinderEnds(elementsCountAcrossMajor, elementsCountAcrossMinor, elementsCountAcrossShell,
                            elementsCountAcrossTransition, shellProportion,
                            [0.0, 0.0, 0.0], cylinderCentralPath.alongAxis[0], cylinderCentralPath.majorAxis[0],
                            cylinderCentralPath.minorRadii[0])
        cylinder1 = CylinderMesh(fieldmodule, coordinates, elementsCountAlong, base,
                                 cylinderShape=cylinderShape,
                                 cylinderCentralPath=cylinderCentralPath, useCrossDerivatives=False)

        # body coordinates
        bodyCoordinates = findOrCreateFieldCoordinates(fieldmodule, name="body coordinates")
        tmp_region = region.createRegion()
        tmp_fieldmodule = tmp_region.getFieldmodule()
        tmp_body_coordinates = findOrCreateFieldCoordinates(tmp_fieldmodule, name="body coordinates")
        tmp_cylinder = CylinderMesh(tmp_fieldmodule, tmp_body_coordinates, elementsCountAlong, base,
                                 cylinderShape=cylinderShape,
                                 cylinderCentralPath=cylinderCentralPath, useCrossDerivatives=False)
        sir = tmp_region.createStreaminformationRegion()
        srm = sir.createStreamresourceMemory()
        tmp_region.write(sir)
        result, buffer = srm.getBuffer()
        sir = region.createStreaminformationRegion()
        srm = sir.createStreamresourceMemoryBuffer(buffer)
        region.read(sir)

        del srm
        del sir
        del tmp_body_coordinates
        del tmp_fieldmodule
        del tmp_region

        # Groups of different parts of the body
        is_body = fieldmodule.createFieldConstant(1)
        bodyMeshGroup = bodyGroup.getMeshGroup(mesh)
        bodyMeshGroup.addElementsConditional(is_body)

        coreMeshGroup = coreGroup.getMeshGroup(mesh)

        # core group
        e1a = elementsCountAcrossShell
        e1z = elementsCountAcrossMinor - elementsCountAcrossShell - 1
        e2a = elementsCountAcrossShell
        e2b = e2a + elementsCountAcrossTransition
        e2z = elementsCountAcrossMajor - elementsCountAcrossShell - 1
        e2y = e2z - elementsCountAcrossTransition
        e1oc = elementsCountAcrossMinor - 2*elementsCountAcrossShell - 2*elementsCountAcrossTransition
        e2oc = elementsCountAcrossMajor - 2*elementsCountAcrossShell - 2*elementsCountAcrossTransition
        e2oCore = e2oc * e1oc + 2 * elementsCountAcrossTransition * (e2oc + e1oc)
        elementsCountAround = cylinder1.getElementsCountAround()
        e2oShell = elementsCountAround * elementsCountAcrossShell
        e2o = e2oCore + e2oShell
        elementId = cylinder1.getElementIdentifiers()
        for e3 in range(elementsCountAlong):
            for e2 in range(elementsCountAcrossMajor):
                for e1 in range(elementsCountAcrossMinor):
                    coreElement = ((e2 >= e2a) and (e2 <= e2z)) and ((e1 >= e1a) and (e1 <= e1z))
                    if coreElement:
                        elementIdentifier = elementId[e3][e2][e1]
                        if elementIdentifier:
                            element = mesh.findElementByIdentifier(elementIdentifier)
                            coreMeshGroup.addElement(element)

        is_non_core = fieldmodule.createFieldNot(coreGroup.getGroup())
        non_coreMeshGroup = non_coreGroup.getMeshGroup(mesh)
        non_coreMeshGroup.addElementsConditional(is_non_core)

        abdomenMeshGroup = abdomenGroup.getMeshGroup(mesh)
        thoraxMeshGroup = thoraxGroup.getMeshGroup(mesh)
        neckMeshGroup = neckGroup.getMeshGroup(mesh)
        headMeshGroup = headGroup.getMeshGroup(mesh)
        meshGroups = [abdomenMeshGroup, thoraxMeshGroup, neckMeshGroup, headMeshGroup]

        abdomenRange = [1, elementsCountAlongAbdomen*e2o]
        thoraxRange = [abdomenRange[1]+1, abdomenRange[1]+elementsCountAlongThorax*e2o]
        neckRange = [thoraxRange[1]+1, thoraxRange[1] + elementsCountAlongNeck*e2o]
        headRange = [neckRange[1]+1, elementsCountAlong*e2o]
        groupsRanges = [abdomenRange, thoraxRange, neckRange, headRange]

        totalElements = e2o*elementsCountAlong
        for elementIdentifier in range(1, totalElements+1):
            element = mesh.findElementByIdentifier(elementIdentifier)
            if coreMeshGroup.containsElement(element):
                ri = 0
                for groupRange in groupsRanges:
                    if (elementIdentifier >= groupRange[0]) and (elementIdentifier <= groupRange[1]):
                        meshGroups[ri].addElement(element)
                        break
                    ri += 1

        if discontinuity:
            # create discontinuity in d3 on the core boundary
            nodes = fieldmodule.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
            elementtemplate = mesh.createElementtemplate()
            undefineNodetemplate = nodes.createNodetemplate()
            undefineNodetemplate.undefineField(coordinates)
            nodetemplate = nodes.createNodetemplate()
            fieldcache = fieldmodule.createFieldcache()
            with ChangeManager(fieldmodule):
                localNodeIndexes = [1, 2, 3, 4]
                valueLabel = Node.VALUE_LABEL_D_DS3
                for e3 in range(elementsCountAlong):
                    for e2 in range(elementsCountAcrossMajor):
                        for e1 in range(elementsCountAcrossMinor):
                            regularRowElement = (((e2 >= e2b) and (e2 <= e2y)) and ((e1 == e1a - 1) or (e1 == e1z + 1)))
                            non_coreFirstLayerElement = (e2 == e2a - 1) or regularRowElement or (e2 == e2z + 1)
                            elementIdentifier = elementId[e3][e2][e1]
                            if elementIdentifier and non_coreFirstLayerElement:
                                element = mesh.findElementByIdentifier(elementIdentifier)
                                eft = element.getElementfieldtemplate(coordinates, -1)
                                nodeIds = get_element_node_identifiers(element, eft)
                                for localNodeIndex in localNodeIndexes:
                                    node = element.getNode(eft, localNodeIndex)
                                    nodetemplate.defineFieldFromNode(coordinates, node)
                                    versionsCount = nodetemplate.getValueNumberOfVersions(coordinates, -1, valueLabel)
                                    if versionsCount == 1:
                                        fieldcache.setNode(node)
                                        result0, x = coordinates.getNodeParameters(fieldcache, -1, Node.VALUE_LABEL_VALUE, 1, 3)
                                        result0, d1 = coordinates.getNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D_DS1, 1, 3)
                                        result0, d2 = coordinates.getNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D_DS2, 1, 3)
                                        result0, d3 = coordinates.getNodeParameters(fieldcache, -1, valueLabel, 1, 3)
                                        result1 = node.merge(undefineNodetemplate)
                                        result2 = nodetemplate.setValueNumberOfVersions(coordinates, -1, valueLabel, 2)
                                        result3 = node.merge(nodetemplate)
                                        result4 = coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_VALUE, 1, x)
                                        result4 = coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D_DS1, 1, d1)
                                        result4 = coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D_DS2, 1, d2)
                                        result4 = coordinates.setNodeParameters(fieldcache, -1, valueLabel, 1, d3)
                                        result5 = coordinates.setNodeParameters(fieldcache, -1, valueLabel, 2, d3)
                                remapEftNodeValueLabelsVersion(eft, localNodeIndexes, [valueLabel], 2)
                                result1 = elementtemplate.defineField(coordinates, -1, eft)
                                result2 = element.merge(elementtemplate)
                                result3 = element.setNodesByIdentifier(eft, nodeIds)
        else:
            fieldcache = fieldmodule.createFieldcache()

        # Annotation fiducial point
        markerGroup = findOrCreateFieldGroup(fieldmodule, "marker")
        markerName = findOrCreateFieldStoredString(fieldmodule, name="marker_name")
        markerLocation = findOrCreateFieldStoredMeshLocation(fieldmodule, mesh, name="marker_location")
        markerBodyCoordinates = findOrCreateFieldCoordinates(fieldmodule, name="marker_body_coordinates")
        nodes = fieldmodule.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
        markerPoints = findOrCreateFieldNodeGroup(markerGroup, nodes).getNodesetGroup()
        markerTemplateInternal = nodes.createNodetemplate()
        markerTemplateInternal.defineField(markerName)
        markerTemplateInternal.defineField(markerLocation)
        markerTemplateInternal.defineField(markerBodyCoordinates)
        #
        middleLeft = elementsCountAcrossMinor//2
        topElem = elementsCountAcrossMajor - 1
        middleRight = middleLeft - 1
        neckFirstElem = elementsCountAlongAbdomen+elementsCountAlongThorax
        thoraxFirstElem = elementsCountAlongAbdomen
        middleDown = elementsCountAcrossMajor//2 - 1

        # organ landmarks groups
        apexOfHeart = heart_terms.get_heart_term('apex of heart')
        leftAtriumEpicardiumVenousMidpoint = heart_terms.get_heart_term('left atrium epicardium venous midpoint')
        rightAtriumEpicardiumVenousMidpoint = heart_terms.get_heart_term('right atrium epicardium venous midpoint')
        apexOfUrinaryBladder = bladder_terms.get_bladder_term('apex of urinary bladder')
        leftUreterJunctionWithBladder = bladder_terms.get_bladder_term('left ureter junction with bladder')
        rightUreterJunctionWithBladder = bladder_terms.get_bladder_term('right ureter junction with bladder')
        urethraJunctionWithBladderDorsal = bladder_terms.get_bladder_term('urethra junction of dorsal bladder neck')
        urethraJunctionWithBladderVentral = bladder_terms.get_bladder_term('urethra junction of ventral bladder neck')
        gastroesophagalJunctionOnLesserCurvature = stomach_terms.get_stomach_term('esophagogastric junction along the lesser curvature on serosa')
        limitingRidgeOnGreaterCurvature = stomach_terms.get_stomach_term('limiting ridge at the greater curvature on serosa')
        pylorusOnGreaterCurvature = stomach_terms.get_stomach_term('gastroduodenal junction along the greater curvature on serosa')
        junctionBetweenFundusAndBodyOnGreaterCurvature = stomach_terms.get_stomach_term("fundus-body junction along the greater curvature on serosa")
        apexOfLeftLung = lung_terms.get_lung_term('apex of left lung')
        ventralBaseOfLeftLung = lung_terms.get_lung_term('ventral base of left lung')
        dorsalBaseOfLeftLung = lung_terms.get_lung_term('dorsal base of left lung')
        apexOfRightLung = lung_terms.get_lung_term('apex of right lung')
        ventralBaseOfRightLung = lung_terms.get_lung_term('ventral base of right lung')
        dorsalBaseOfRightLung = lung_terms.get_lung_term('dorsal base of right lung')
        laterodorsalTipOfMiddleLobeOfRightLung = lung_terms.get_lung_term('laterodorsal tip of middle lobe of right lung')
        apexOfRightLungAccessoryLobe = lung_terms.get_lung_term('apex of right lung accessory lobe')
        ventralBaseOfRightLungAccessoryLobe = lung_terms.get_lung_term('ventral base of right lung accessory lobe')
        dorsalBaseOfRightLungAccessoryLobe = lung_terms.get_lung_term('dorsal base of right lung accessory lobe')
        medialBaseOfLeftLung = lung_terms.get_lung_term("medial base of left lung")
        medialBaseOfRightLung = lung_terms.get_lung_term("medial base of right lung")
        brainstemDorsalMidlineCaudalPoint = brainstem_terms.get_brainstem_term('brainstem dorsal midline caudal point')
        brainstemDorsalMidlineCranialPoint = brainstem_terms.get_brainstem_term('brainstem dorsal midline cranial point')
        brainstemVentralMidlineCaudalPoint = brainstem_terms.get_brainstem_term('brainstem ventral midline caudal point')
        brainstemVentralMidlineCranialPoint = brainstem_terms.get_brainstem_term('brainstem ventral midline cranial point')

        # marker coordinates. In future we want to have only one table for all species.
        if isRat:
            bodyMarkerPoints = [
                {"group": ("left hip joint", ''), "x": [0.367, 0.266, 0.477]},
                {"group": ("right hip joint", ''), "x": [-0.367, 0.266, 0.477]},
                {"group": ("left shoulder joint", ''), "x": [0.456, -0.071, 2.705]},
                {"group": ("right shoulder joint", ''), "x": [-0.456, -0.071, 2.705]},
                {"group": ("along left femur", ''), "x": [0.456, 0.07, 0.633]},
                {"group": ("along right femur", ''), "x": [-0.456, 0.07, 0.633]},
                {"group": ("along left humerus", ''), "x": [0.423, -0.173, 2.545]},
                {"group": ("along right humerus", ''), "x": [-0.423, -0.173, 2.545]},
                {"group": apexOfUrinaryBladder, "x": [-0.124, -0.383, 0.434]},
                {"group": leftUreterJunctionWithBladder, "x": [-0.111, -0.172, 0.354]},
                {"group": rightUreterJunctionWithBladder, "x": [-0.03, -0.196, 0.363]},
                {"group": urethraJunctionWithBladderDorsal, "x": [-0.03, -0.26, 0.209]},
                {"group": urethraJunctionWithBladderVentral, "x": [-0.037, -0.304, 0.203]},
                {"group": brainstemDorsalMidlineCaudalPoint, "x": [-0.032, 0.418, 2.713]},
                {"group": brainstemDorsalMidlineCranialPoint, "x": [-0.017, 0.203, 2.941]},
                {"group": brainstemVentralMidlineCaudalPoint, "x": [-0.028, 0.388, 2.72]},
                {"group": brainstemVentralMidlineCranialPoint, "x": [-0.019, 0.167, 2.95]},
                {"group": apexOfHeart, "x": [0.096, -0.128, 1.601]},
                {"group": leftAtriumEpicardiumVenousMidpoint, "x": [0.127, -0.083, 2.079]},
                {"group": rightAtriumEpicardiumVenousMidpoint, "x": [0.039, -0.082, 2.075]},
                {"group": apexOfLeftLung, "x": [0.172, -0.175, 2.337]},
                {"group": ventralBaseOfLeftLung, "x": [0.274, -0.285, 1.602]},
                {"group": dorsalBaseOfLeftLung, "x": [0.037, 0.31, 1.649]},
                {"group": apexOfRightLung, "x": [-0.086, -0.096, 2.311]},
                {"group": ventralBaseOfRightLung, "x": [0.14, -0.357, 1.662]},
                {"group": dorsalBaseOfRightLung, "x": [-0.054, 0.304, 1.667]},
                {"group": laterodorsalTipOfMiddleLobeOfRightLung, "x": [-0.258, -0.173, 2.013]},
                {"group": apexOfRightLungAccessoryLobe, "x": [0.041, -0.063, 1.965]},
                {"group": ventralBaseOfRightLungAccessoryLobe, "x": [0.143, -0.356, 1.66]},
                {"group": dorsalBaseOfRightLungAccessoryLobe, "x": [0.121, -0.067, 1.627]},
                {"group": gastroesophagalJunctionOnLesserCurvature, "x": [0.12, 0.009, 1.446]},
                {"group": limitingRidgeOnGreaterCurvature, "x": [0.318, 0.097, 1.406]},
                {"group": pylorusOnGreaterCurvature, "x": [0.08, -0.111, 1.443]},
            ]
        elif isHuman:
            bodyMarkerPoints = [
                {"group": urethraJunctionWithBladderDorsal, "x": [-0.0071, -0.2439, 0.1798]},
                {"group": urethraJunctionWithBladderVentral, "x": [-0.007, -0.2528, 0.1732]},
                {"group": leftUreterJunctionWithBladder, "x": [0.1074, 0.045, 0.1728]},
                {"group": rightUreterJunctionWithBladder, "x": [-0.1058, 0.0533, 0.1701]},
                {"group": apexOfUrinaryBladder, "x": [0.005, 0.1286, 0.1264]},
                {"group": brainstemDorsalMidlineCaudalPoint, "x": [0.0068, 0.427, 2.7389]},
                {"group": brainstemDorsalMidlineCranialPoint, "x": [0.008, -0.0231, 3.0778]},
                {"group": brainstemVentralMidlineCaudalPoint, "x": [0.0054, 0.3041, 2.7374]},
                {"group": brainstemVentralMidlineCranialPoint, "x": [0.0025, -0.2308, 3.091]},
                {"group": apexOfHeart, "x": [0.1373, -0.1855, 1.421]},
                {"group": leftAtriumEpicardiumVenousMidpoint, "x": [0.0024, 0.1452, 1.8022]},
                {"group": rightAtriumEpicardiumVenousMidpoint, "x": [-0.0464, 0.0373, 1.7491]},
                {"group": apexOfLeftLung, "x": [0.0655, -0.0873, 2.3564]},
                {"group": apexOfRightLung, "x": [-0.088, -0.0363, 2.3518]},
                {"group": laterodorsalTipOfMiddleLobeOfRightLung, "x": [-0.2838, -0.0933, 1.9962]},
                {"group": ventralBaseOfLeftLung, "x": [0.219, -0.2866, 1.4602]},
                {"group": medialBaseOfLeftLung, "x": [0.0426, -0.0201, 1.4109]},
                {"group": ventralBaseOfRightLung, "x": [-0.2302, -0.2356, 1.3926]},
                {"group": medialBaseOfRightLung, "x": [-0.0363, 0.0589, 1.3984]},
                {"group": dorsalBaseOfLeftLung, "x": [0.1544, 0.2603, 1.3691]},
                {"group": dorsalBaseOfRightLung, "x": [0.0369, -0.2524, 0.912]},
                {"group": gastroesophagalJunctionOnLesserCurvature, "x": [-0.0062, -0.3259, 0.8586]},
                {"group": pylorusOnGreaterCurvature, "x": [-0.0761, -0.3189, 0.8663]},
                {"group": junctionBetweenFundusAndBodyOnGreaterCurvature, "x": [0.1884, -0.1839, 0.9639]},
            ]

        nodeIdentifier = cylinder1._endNodeIdentifier
        findMarkerLocation = fieldmodule.createFieldFindMeshLocation(markerBodyCoordinates, bodyCoordinates, mesh)
        findMarkerLocation.setSearchMode(FieldFindMeshLocation.SEARCH_MODE_EXACT)
        for bodyMarkerPoint in bodyMarkerPoints:
            markerPoint = markerPoints.createNode(nodeIdentifier, markerTemplateInternal)
            fieldcache.setNode(markerPoint)
            markerBodyCoordinates.assignReal(fieldcache, bodyMarkerPoint["x"])
            markerName.assignString(fieldcache, bodyMarkerPoint["group"][0])

            element, xi = findMarkerLocation.evaluateMeshLocation(fieldcache, 3)
            markerLocation.assignMeshLocation(fieldcache, element, xi)
            nodeIdentifier += 1

        return annotationGroups

    @classmethod
    def refineMesh(cls, meshRefinement, options):
        """
        Refine source mesh into separate region, with change of basis.
        :param meshRefinement: MeshRefinement, which knows source and target region.
        :param options: Dict containing options. See getDefaultOptions().
        """
        assert isinstance(meshRefinement, MeshRefinement)
        refineElementsCountAcrossMajor = options['Refine number of elements across major']
        refineElementsCountAlong = options['Refine number of elements along']
        meshRefinement.refineAllElementsCubeStandard3d(refineElementsCountAcrossMajor, refineElementsCountAlong, refineElementsCountAcrossMajor)

    @classmethod
    def defineFaceAnnotations(cls, region, options, annotationGroups):
        """
        Add face annotation groups from the highest dimension mesh.
        Must have defined faces and added subelements for highest dimension groups.
        :param region: Zinc region containing model.
        :param options: Dict containing options. See getDefaultOptions().
        :param annotationGroups: List of annotation groups for top-level elements.
        New face annotation groups are appended to this list.
        """

        # create 2d surface mesh groups
        fm = region.getFieldmodule()
        mesh2d = fm.findMeshByDimension(2)

        bodyGroup = getAnnotationGroupForTerm(annotationGroups, get_body_term("body"))
        coreGroup = getAnnotationGroupForTerm(annotationGroups, get_body_term("core"))
        non_coreGroup = getAnnotationGroupForTerm(annotationGroups, get_body_term("non core"))
        abdomenGroup = getAnnotationGroupForTerm(annotationGroups, get_body_term("abdomen"))
        thoraxGroup = getAnnotationGroupForTerm(annotationGroups, get_body_term("thorax"))
        neckGroup = getAnnotationGroupForTerm(annotationGroups, get_body_term("neck core"))

        skinGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_body_term("skin epidermis"))
        coreBoundaryGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_body_term("core boundary"))
        diaphragmGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_body_term("diaphragm"))
        spinalCordGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_body_term("spinal cord"))

        is_exterior = fm.createFieldIsExterior()
        is_on_face_xi3_1 = fm.createFieldIsOnFace(Element.FACE_TYPE_XI3_1)
        is_skin = fm.createFieldAnd(is_exterior, is_on_face_xi3_1)

        skinMeshGroup = skinGroup.getMeshGroup(mesh2d)
        skinMeshGroup.addElementsConditional(is_skin)

        is_core_boundary = fm.createFieldAnd(coreGroup.getGroup(), non_coreGroup.getGroup())
        coreBoundaryMeshGroup = coreBoundaryGroup.getMeshGroup(mesh2d)
        coreBoundaryMeshGroup.addElementsConditional(is_core_boundary)

        is_diaphragm = fm.createFieldAnd(abdomenGroup.getGroup(), thoraxGroup.getGroup())
        diaphragmMeshGroup = diaphragmGroup.getMeshGroup(mesh2d)
        diaphragmMeshGroup.addElementsConditional(is_diaphragm)

        # spinal cord
        coordinates = fm.findFieldByName('coordinates').castFiniteElement()
        zero = fm.createFieldConstant(0)
        zero_m = fm.createFieldConstant(-0.01)
        zero_p = fm.createFieldConstant(0.01)
        comp2 = cls.axis2.index(max(cls.axis2)) + 1
        ax2_comp = fm.createFieldComponent(coordinates, comp2)
        ax2_gt_zero_m = fm.createFieldGreaterThan(ax2_comp, zero_m)
        ax2_lt_zero_p = fm.createFieldLessThan(ax2_comp, zero_p)
        ax2_gt_zero_xi10 = fm.createFieldAnd(fm.createFieldIsOnFace(Element.FACE_TYPE_XI1_0), ax2_gt_zero_m)
        ax2_lt_zero_xi10 = fm.createFieldAnd(fm.createFieldIsOnFace(Element.FACE_TYPE_XI1_0), ax2_lt_zero_p)
        is_ax2_zero = fm.createFieldAnd(ax2_lt_zero_xi10, ax2_gt_zero_xi10)
        comp3 = cls.axis3.index(max(cls.axis3)) + 1
        ax3_comp = fm.createFieldComponent(coordinates, comp3)
        ax3_positive = fm.createFieldGreaterThan(ax3_comp, zero)
        is_ax2_zero_ax3_positive = fm.createFieldAnd(is_ax2_zero, ax3_positive)
        is_abdomen_thorax = fm.createFieldAdd(abdomenGroup.getGroup(), thoraxGroup.getGroup())
        is_abdomen_thorax_neck = fm.createFieldAdd(is_abdomen_thorax, neckGroup.getGroup())
        is_abdomen_thorax_neck_boundary = fm.createFieldAnd(is_core_boundary, is_abdomen_thorax_neck)
        is_spinal_cord = fm.createFieldAnd(is_ax2_zero_ax3_positive, is_abdomen_thorax_neck_boundary)

        mesh1d = fm.findMeshByDimension(1)
        spinalCordMeshGroup = spinalCordGroup.getMeshGroup(mesh1d)
        spinalCordMeshGroup.addElementsConditional(is_spinal_cord)
Ejemplo n.º 22
0
 def smooth(self,
            updateDirections=False,
            maxIterations=10,
            arcLengthTolerance=1.0E-6):
     '''
     :param maxIterations: Maximum iterations before stopping if not converging.
     :param arcLengthTolerance: Ratio of difference in arc length from last iteration
     divided by current arc length under which convergence is achieved. Required to
     be met by every element edge.
     '''
     if not self._derivativeMap:
         return  # no nodes being smoothed
     componentsCount = self._field.getNumberOfComponents()
     with ChangeManager(self._fieldmodule):
         fieldcache = self._fieldmodule.createFieldcache()
         for iter in range(maxIterations + 1):
             converged = True
             for edge in self._edgesMap.values():
                 lastArcLength = edge.getLastArcLength()
                 arcLength = edge.evaluateArcLength(self._nodes,
                                                    self._field, fieldcache)
                 edge.updateLastArcLength()
                 if (math.fabs(arcLength - lastArcLength) /
                         arcLength) > arcLengthTolerance:
                     converged = False
             if converged:
                 print('Derivative smoothing: Converged after', iter,
                       'iterations.')
                 break
             elif (iter == maxIterations):
                 print('Derivative smoothing: Stopping after',
                       maxIterations, 'iterations without converging.')
                 break
             for derivativeKey, derivativeEdges in self._derivativeMap.items(
             ):
                 edgeCount = len(derivativeEdges)
                 if edgeCount > 1:
                     nodeIdentifier, nodeValueLabel, nodeVersion = derivativeKey
                     fieldcache.setNode(
                         self._nodes.findNodeByIdentifier(nodeIdentifier))
                     if updateDirections:
                         x = [0.0 for _ in range(componentsCount)]
                     else:
                         result, x = self._field.getNodeParameters(
                             fieldcache, -1, nodeValueLabel, nodeVersion,
                             componentsCount)
                     mag = 0.0
                     for derivativeEdge in derivativeEdges:
                         edge, expressionIndex, totalScaleFactor = derivativeEdge
                         arcLength = edge.getArcLength()
                         if updateDirections:
                             delta = edge.getDelta()
                             if totalScaleFactor < 0.0:
                                 delta = [-d for d in delta]
                             for c in range(componentsCount):
                                 x[c] += delta[c] / arcLength
                         if self._scalingMode == DerivativeScalingMode.ARITHMETIC_MEAN:
                             mag += arcLength / math.fabs(totalScaleFactor)
                         else:  # self._scalingMode == DerivativeScalingMode.HARMONIC_MEAN
                             mag += math.fabs(totalScaleFactor) / arcLength
                     if self._scalingMode == DerivativeScalingMode.ARITHMETIC_MEAN:
                         mag /= edgeCount
                     else:  # self._scalingMode == DerivativeScalingMode.HARMONIC_MEAN
                         mag = edgeCount / mag
                     if (mag <= 0.0):
                         print('Node', nodeIdentifier, 'value', nodeValueLabel, 'version', nodeVersion, \
                               'has negative mag', mag)
                     x = setMagnitude(x, mag)
                     result = self._field.setNodeParameters(
                         fieldcache, -1, nodeValueLabel, nodeVersion, x)
             for derivativeKey, derivativeEdges in self._derivativeMap.items(
             ):
                 edgeCount = len(derivativeEdges)
                 if edgeCount == 1:
                     # boundary smoothing over single edge
                     nodeIdentifier, nodeValueLabel, nodeVersion = derivativeKey
                     edge, expressionIndex, totalScaleFactor = derivativeEdges[
                         0]
                     # re-evaluate arc length so parameters are up-to-date for other end
                     arcLength = edge.evaluateArcLength(
                         self._nodes, self._field, fieldcache)
                     fieldcache.setNode(
                         self._nodes.findNodeByIdentifier(nodeIdentifier)
                     )  # since changed by evaluateArcLength
                     otherExpressionIndex = 3 if (expressionIndex
                                                  == 1) else 1
                     otherd = edge.getParameter(otherExpressionIndex)
                     if updateDirections:
                         thisx = edge.getParameter(expressionIndex - 1)
                         otherx = edge.getParameter(otherExpressionIndex -
                                                    1)
                         bothEndsOnBoundary = False
                         otherExpression = edge.getExpression(
                             otherExpressionIndex)
                         if len(otherExpression) == 1:
                             otherNodeIdentifier, otherValueLabel, otherNodeVersion, otherTotalScaleFactor = otherExpression[
                                 0]
                             otherDerivativeKey = (otherNodeIdentifier,
                                                   otherValueLabel,
                                                   otherNodeVersion)
                             otherDerivativeEdges = self._derivativeMap.get(
                                 otherDerivativeKey)
                             bothEndsOnBoundary = (
                                 otherDerivativeEdges
                                 is not None) and (len(otherDerivativeEdges)
                                                   == 1)
                         if bothEndsOnBoundary:
                             if expressionIndex == 1:
                                 x = [(otherx[c] - thisx[c])
                                      for c in range(componentsCount)]
                             else:
                                 x = [(thisx[c] - otherx[c])
                                      for c in range(componentsCount)]
                         else:
                             if expressionIndex == 1:
                                 x = interpolateLagrangeHermiteDerivative(
                                     thisx, otherx, otherd, 0.0)
                             else:
                                 x = interpolateHermiteLagrangeDerivative(
                                     otherx, otherd, thisx, 1.0)
                         x = [d / totalScaleFactor for d in x]
                     else:
                         result, x = self._field.getNodeParameters(
                             fieldcache, -1, nodeValueLabel, nodeVersion,
                             componentsCount)
                     othermag = magnitude(otherd)
                     mag = (2.0 * arcLength -
                            othermag) / math.fabs(totalScaleFactor)
                     if (mag <= 0.0):
                         print('Derivative smoothing: Node', nodeIdentifier,
                               'label', nodeValueLabel, 'version',
                               nodeVersion, 'has negative magnitude', mag)
                     x = setMagnitude(x, mag)
                     result = self._field.setNodeParameters(
                         fieldcache, -1, nodeValueLabel, nodeVersion, x)
         # record modified nodes while ChangeManager is in effect
         if self._editNodesetGroup:
             for derivativeKey in self._derivativeMap:
                 self._editNodesetGroup.addNode(
                     (self._nodes.findNodeByIdentifier(derivativeKey[0])))
         del fieldcache
Ejemplo n.º 23
0
def createOrganCoordinates(xiList, relativeThicknessList,
                           lengthToDiameterRatio, wallThicknessToDiameterRatio,
                           elementsCountAround, elementsCountAlong,
                           elementsCountThroughWall, transitElementList):
    """
    Calculates organ coordinates and derivatives represented by a cylindrical tube with a unit inner diameter,
    length equivalent to lengthToDiameterRatio and wall thickness of wallThicknessToDiameterRatio.
    :param xiList: List containing xi for each point around the outer surface of the tube.
    :param relativeThicknessList: Relative thickness of each element through wall for organ coordinates.
    :param lengthToDiameterRatio: Ratio of total length along organ to inner diameter of organ
    :param wallThicknessToDiameterRatio: Ratio of wall thickness to inner diameter of organ.
    :param elementsCountAround: Number of elements around tube.
    :param elementsCountAlong: Number of elements along tube.
    :param elementsCountThroughWall: Number of elements through wall.
    :param transitElementList: stores true if element around is a transition element between a big and small element.
    :return: coordinates and derivatives of organ coordinates field.
    """
    if relativeThicknessList:
        xi3 = 0.0
        xi3List = [0.0]
        for n3 in range(elementsCountThroughWall):
            xi3 += relativeThicknessList[n3]
            xi3List.append(xi3)
        relativeThicknessList.append(relativeThicknessList[-1])

    # Calculate organ coordinates and derivatives
    xOrganList = []
    d1OrganList = []
    d2OrganList = []
    d2 = [0.0, lengthToDiameterRatio / elementsCountAlong, 0.0]

    for n2 in range(elementsCountAlong + 1):
        cx = [0.0, lengthToDiameterRatio / elementsCountAlong * n2, 0.0]
        xiFace = xiList[n2]
        for n3 in range(elementsCountThroughWall + 1):
            xi3 = xi3List[
                n3] if relativeThicknessList else 1.0 / elementsCountThroughWall * n3
            radius = 0.5 + wallThicknessToDiameterRatio * xi3
            axis1 = [0.0, 0.0, radius]
            axis2 = [radius, 0.0, 0.0]
            d1List = []
            for n1 in range(len(xiFace) - 1):
                dTheta = (xiFace[n1 + 1 if n1 < len(xiFace) - 1 else 0] -
                          xiFace[n1]) * 2.0 * math.pi
                radiansAround = 2.0 * math.pi * xiFace[n1]
                cosRadiansAround = math.cos(radiansAround)
                sinRadiansAround = math.sin(radiansAround)
                xOrganList.append([(cx[c] + cosRadiansAround * axis1[c] +
                                    sinRadiansAround * axis2[c])
                                   for c in range(3)])
                d1List.append([
                    dTheta * (-sinRadiansAround * axis1[c] +
                              cosRadiansAround * axis2[c]) for c in range(3)
                ])
                d2OrganList.append(d2)

            # To modify derivative along transition elements
            for i in range(len(transitElementList)):
                if transitElementList[i]:
                    d1List[i] = vector.setMagnitude(
                        d1List[i], vector.magnitude(d1List[i - 1]))
                    d1List[i + 1] = vector.setMagnitude(
                        d1List[i + 1],
                        vector.magnitude(d1List[(i + 2) %
                                                elementsCountAround]))

            d1OrganList += d1List

    return xOrganList, d1OrganList, d2OrganList
Ejemplo n.º 24
0
def sampleCubicHermiteCurves(nx,
                             nd1,
                             elementsCountOut,
                             addLengthStart=0.0,
                             addLengthEnd=0.0,
                             lengthFractionStart=1.0,
                             lengthFractionEnd=1.0,
                             elementLengthStartEndRatio=1.0,
                             arcLengthDerivatives=False):
    """
    Get systematically spaced points and derivatives over cubic Hermite interpolated
    curves with nodes nx and derivatives nd1. The first element uses the first two nodes.
    :param nx: Coordinates of nodes along curves.
    :param nd1: Derivatives of nodes along curves.
    :param addLengthStart, addLengthEnd: Extra length to add to start and end elements.
    :param lengthFractionStart, lengthFractionEnd: Fraction of mid element length for
        start and end elements. Can use in addition to AddLengths: If LengthFraction
        is 0.5 and AddLength is derivative/2.0 can blend into known derivative at start
        or end.
    :param elementLengthStartEndRatio: Start/end element length ratio, with lengths
        smoothly varying in between. Requires at least 2 elements. Applied in proportion
        to lengthFractionStart, lengthFractionEnd.
    :param arcLengthDerivatives: If True each cubic section is rescaled to arc length.
    If False (default), derivatives and distances are used as supplied.
    :return: px[], pd1[], pe[], pxi[], psf[], where pe[] and pxi[] are lists of element indices and
    and xi locations in the 'in' elements to pass to partner interpolateSample functions. psf[] is
    a list of scale factors for converting derivatives from old to new xi coordinates: dxi(old)/dxi(new).
    """
    elementsCountIn = len(nx) - 1
    assert (elementsCountIn > 0) and (len(nd1) == (elementsCountIn + 1)) and \
        (elementsCountOut > 0), 'sampleCubicHermiteCurves.  Invalid arguments'
    lengths = [0.0]
    nd1a = []
    nd1b = []
    length = 0.0
    for e in range(elementsCountIn):
        if arcLengthDerivatives:
            arcLength = computeCubicHermiteArcLength(nx[e],
                                                     nd1[e],
                                                     nx[e + 1],
                                                     nd1[e + 1],
                                                     rescaleDerivatives=True)
            nd1a.append(vector.setMagnitude(nd1[e], arcLength))
            nd1b.append(vector.setMagnitude(nd1[e + 1], arcLength))
        else:
            arcLength = getCubicHermiteArcLength(nx[e], nd1[e], nx[e + 1],
                                                 nd1[e + 1])
        length += arcLength
        lengths.append(length)
    proportionEnd = 2.0 / (elementLengthStartEndRatio + 1)
    proportionStart = elementLengthStartEndRatio * proportionEnd
    if elementsCountOut == 1:
        elementLengthMid = length
    else:
        elementLengthMid = (length - addLengthStart - addLengthEnd) / \
            (elementsCountOut - 2.0 + proportionStart*lengthFractionStart + proportionEnd*lengthFractionEnd)
    elementLengthProportionStart = proportionStart * lengthFractionStart * elementLengthMid
    elementLengthProportionEnd = proportionEnd * lengthFractionEnd * elementLengthMid
    # get smoothly varying element lengths, not accounting for start and end
    if (elementsCountOut == 1) or (elementLengthStartEndRatio == 1.0):
        elementLengths = [elementLengthMid] * elementsCountOut
    else:
        elementLengths = []
        for eOut in range(elementsCountOut):
            xi = eOut / (elementsCountOut - 1)
            elementLengths.append(
                ((1.0 - xi) * proportionStart + xi * proportionEnd) *
                elementLengthMid)
    # get middle derivative magnitudes
    nodeDerivativeMagnitudes = [None] * (elementsCountOut + 1
                                         )  # start and end determined below
    for n in range(1, elementsCountOut):
        nodeDerivativeMagnitudes[n] = 0.5 * (elementLengths[n - 1] +
                                             elementLengths[n])
    # fix end lengths:
    elementLengths[0] = addLengthStart + elementLengthProportionStart
    elementLengths[-1] = addLengthEnd + elementLengthProportionEnd
    #print('\nsampleCubicHermiteCurves:')
    #print('  elementLengths', elementLengths, 'addLengthStart', addLengthStart, 'addLengthEnd', addLengthEnd)
    #print('  sum lengths', sum(elementLengths), 'vs. length', length, 'diff', sum(elementLengths) - length)
    # set end derivatives:
    if elementsCountOut == 1:
        nodeDerivativeMagnitudes[0] = nodeDerivativeMagnitudes[
            1] = elementLengths[0]
    else:
        nodeDerivativeMagnitudes[
            0] = elementLengths[0] * 2.0 - nodeDerivativeMagnitudes[1]
        nodeDerivativeMagnitudes[
            -1] = elementLengths[-1] * 2.0 - nodeDerivativeMagnitudes[-2]

    px = []
    pd1 = []
    pe = []
    pxi = []
    psf = []
    distance = 0.0
    e = 0
    for eOut in range(elementsCountOut):
        while e < elementsCountIn:
            if distance < lengths[e + 1]:
                partDistance = distance - lengths[e]
                if arcLengthDerivatives:
                    xi = partDistance / (lengths[e + 1] - lengths[e])
                    x = interpolateCubicHermite(nx[e], nd1a[e], nx[e + 1],
                                                nd1b[e], xi)
                    d1 = interpolateCubicHermiteDerivative(
                        nx[e], nd1a[e], nx[e + 1], nd1b[e], xi)
                else:
                    x, d1, _eIn, xi = getCubicHermiteCurvesPointAtArcDistance(
                        nx[e:e + 2], nd1[e:e + 2], partDistance)
                sf = nodeDerivativeMagnitudes[eOut] / vector.magnitude(d1)
                px.append(x)
                pd1.append([sf * d for d in d1])
                pe.append(e)
                pxi.append(xi)
                psf.append(sf)
                break
            e += 1
        distance += elementLengths[eOut]
    e = elementsCountIn
    eOut = elementsCountOut
    xi = 1.0
    d1 = nd1[e]
    sf = nodeDerivativeMagnitudes[eOut] / vector.magnitude(d1)
    px.append(nx[e])
    pd1.append([sf * d for d in d1])
    pe.append(e - 1)
    pxi.append(xi)
    psf.append(sf)
    return px, pd1, pe, pxi, psf
    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
Ejemplo n.º 26
0
def smoothCubicHermiteDerivativesLoop(
        nx,
        nd1,
        fixAllDirections=False,
        magnitudeScalingMode=DerivativeScalingMode.ARITHMETIC_MEAN,
        instrument=False):
    """
    Modifies derivatives nd1 to be smoothly varying and near arc length.
    Values are treated as being in a loop, so the first point follows the last.
    Assumes initial derivatives are zero or reasonable.
    Where directions are smoothed the weighted/harmonic mean is used.
    :param nx: List of coordinates of nodes along curves.
    :param nd1: List of derivatives of nodes along curves.
    :param fixAllDirections: Set to True to only smooth magnitudes, otherwise both direction and magnitude are adjusted.
    :param magnitudeScalingMode: A value from enum DerivativeScalingMode specifying
    expression used to get derivative magnitude from adjacent arc lengths.
    :return: Modified nd1
    """
    nodesCount = elementsCount = len(nx)
    assert elementsCount > 1, 'smoothCubicHermiteDerivativesLoop.  Too few nodes/elements'
    assert len(
        nd1
    ) == elementsCount, 'smoothCubicHermiteDerivativesLoop.  Mismatched number of derivatives'
    arithmeticMeanMagnitude = magnitudeScalingMode is DerivativeScalingMode.ARITHMETIC_MEAN
    assert arithmeticMeanMagnitude or (magnitudeScalingMode is DerivativeScalingMode.HARMONIC_MEAN), \
        'smoothCubicHermiteDerivativesLine. Invalid magnitude scaling mode'
    md1 = copy.copy(nd1)
    componentsCount = len(nx[0])
    componentRange = range(componentsCount)
    tol = 1.0E-6
    if instrument:
        print('iter 0', md1)
    for iter in range(100):
        lastmd1 = copy.copy(md1)
        arcLengths = [
            getCubicHermiteArcLength(nx[e], md1[e],
                                     nx[(e + 1) % elementsCount],
                                     md1[(e + 1) % elementsCount])
            for e in range(elementsCount)
        ]
        for n in range(nodesCount):
            nm = n - 1
            if not fixAllDirections:
                # get mean of directions from point n to points (n - 1) and (n + 1)
                np = (n + 1) % nodesCount
                dirm = [(nx[n][c] - nx[nm][c]) for c in componentRange]
                dirp = [(nx[np][c] - nx[n][c]) for c in componentRange]
                # mean weighted by fraction towards that end, equivalent to harmonic mean
                arcLengthmp = arcLengths[nm] + arcLengths[n]
                wm = arcLengths[n] / arcLengthmp
                wp = arcLengths[nm] / arcLengthmp
                md1[n] = [(wm * dirm[c] + wp * dirp[c])
                          for c in componentRange]
            if arithmeticMeanMagnitude:
                mag = 0.5 * (arcLengths[nm] + arcLengths[n])
            else:  # harmonicMeanMagnitude
                mag = 2.0 / (1.0 / arcLengths[nm] + 1.0 / arcLengths[n])
            md1[n] = vector.setMagnitude(md1[n], mag)
        if instrument:
            print('iter', iter + 1, md1)
        dtol = tol * sum(arcLengths) / len(arcLengths)
        for n in range(nodesCount):
            for c in componentRange:
                if math.fabs(md1[n][c] - lastmd1[n][c]) > dtol:
                    break
            else:
                continue
            break
        else:
            if instrument:
                print(
                    'smoothCubicHermiteDerivativesLoop converged after iter:',
                    iter)
            return md1

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    elementIdentifier = nextElementIdentifier

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

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

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

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

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

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

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

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

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

    fm.endChange()

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

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

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

    # track points in shape of ostium

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

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

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

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

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

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

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

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

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

    nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)

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

    nodeIdentifier = startNodeIdentifier

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

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

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

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

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

    mesh = fm.findMeshByDimension(3)
    elementIdentifier = startElementIdentifier

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

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

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

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

    fm.endChange()
    return nodeIdentifier, elementIdentifier, (ox, od1, od2, od3, oNodeId, oPositions)