Esempio n. 1
0
def computeCubicHermiteArcLength(v1, d1, v2, d2, rescaleDerivatives):
    """
    Compute arc length between v1 and v2, scaling unit d1 and d2.
    Iterative; not optimised.
    :param d1: Initial derivative at v1.
    :param d2: Initial derivative at v2.
    :param rescaleDerivatives: If True, rescale initial d1 and d2 to |v2 - v|
    :return: Arc length.
    """
    if rescaleDerivatives:
        lastArcLength = math.sqrt(
            sum((v2[i] - v1[i]) * (v2[i] - v1[i]) for i in range(len(v1))))
    else:
        lastArcLength = getCubicHermiteArcLength(v1, d1, v2, d2)
    d1 = vector.normalise(d1)
    d2 = vector.normalise(d2)
    tol = 1.0E-6
    for iters in range(100):
        #print('iter',iters,'=',lastArcLength)
        d1s = [lastArcLength * d for d in d1]
        d2s = [lastArcLength * d for d in d2]
        arcLength = getCubicHermiteArcLength(v1, d1s, v2, d2s)
        if iters > 9:
            arcLength = 0.8 * arcLength + 0.2 * lastArcLength
        if math.fabs(arcLength - lastArcLength) < tol * arcLength:
            #print('computeCubicHermiteArcLength converged at iter',iters,'=',arcLength,', closeness', math.fabs(arcLength - lastArcLength))
            return arcLength
        lastArcLength = arcLength
    print('computeCubicHermiteArcLength:  Max iters reached:', iters, '=',
          arcLength, ', closeness', math.fabs(arcLength - lastArcLength))
    return arcLength
Esempio n. 2
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
Esempio n. 3
0
def getCircleProjectionAxes(ax,
                            ad1,
                            ad2,
                            ad3,
                            length,
                            angle1radians,
                            angle2radians,
                            angle3radians=None):
    '''
    Project coordinates and orthogonal unit axes ax, ad1, ad2, ad3 by length in
    direction of ad3, rotated towards ad1 by angle1radians and ad2 by
    angle2radians such that it conforms to a circular arc of the given length,
    tangential to ad3 at the start and the final bd3 at the end.
    All vectors must be length 3.
    Assumes angles 1 and 2 are not large e.g. less than 90 degrees.
    Note: not robust for all inputs.
    :param angle3radians: Optional final rotation of projection axes about bd3.
    :return: Final coordinates and orthogonal unit axes: bx, bd1, bd2, bd3
    '''
    if (math.fabs(angle1radians) < 0.000001) and (math.fabs(angle2radians) <
                                                  0.000001):
        bx = [(ax[c] + length * ad3[c]) for c in range(3)]
        bd1 = copy.deepcopy(ad1)
        bd2 = copy.deepcopy(ad2)
        bd3 = copy.deepcopy(ad3)
    else:
        cosAngle1 = math.cos(angle1radians)
        sinAngle1 = math.sin(angle1radians)
        cosAngle2 = math.cos(angle2radians)
        sinAngle2 = math.sin(angle2radians)
        f1 = sinAngle1 * cosAngle2
        f2 = cosAngle1 * sinAngle2
        f3 = cosAngle1 * cosAngle2
        angleAroundRadians = math.atan2(f2, f1)
        fh = math.sqrt(f1 * f1 + f2 * f2)
        arcAngleRadians = 0.5 * math.pi - math.atan2(f3, fh)
        arcRadius = length / arcAngleRadians
        br = arcRadius * (1.0 - math.cos(arcAngleRadians))
        w1 = br * math.cos(angleAroundRadians)  # f1/fh
        w2 = br * math.sin(angleAroundRadians)  # f2/fh
        w3 = arcRadius * math.sin(arcAngleRadians)
        bx = [(ax[c] + w1 * ad1[c] + w2 * ad2[c] + w3 * ad3[c])
              for c in range(3)]
        bd3 = vector.normalise([(f1 * ad1[c] + f2 * ad2[c] + f3 * ad3[c])
                                for c in range(3)])
        bd1 = vector.normalise(vector.crossproduct3(ad2, bd3))
        bd2 = vector.crossproduct3(bd3, bd1)
    if angle3radians:
        cosAngle3 = math.cos(angle3radians)
        sinAngle3 = math.sin(angle3radians)
        bd1, bd2 = [
            (cosAngle3 * bd1[c] + sinAngle3 * bd2[c]) for c in range(3)
        ], [(cosAngle3 * bd2[c] - sinAngle3 * bd1[c]) for c in range(3)]
    return bx, bd1, bd2, bd3
def calculate_surface_axes(d1, d2, direction):
    '''
    :return: Vectors ax1, ax2, ax3: ax1 in-plane in 3-D vector direction,
    ax2 in-plane normal to a and ax3 normal to the surface plane.
    Vectors all have unit magnitude.
    '''
    ax3 = vector.normalise(vector.crossproduct3(d1, d2))
    delta_xi1, delta_xi2 = calculate_surface_delta_xi(d1, d2, direction)
    ax1 = vector.normalise([ delta_xi1*d1[c] + delta_xi2*d2[c] for c in range(3) ])
    ax2 = vector.normalise(vector.crossproduct3(ax3, ax1))
    return ax1, ax2, ax3
Esempio n. 5
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
Esempio n. 6
0
def createEllipsePoints(cx,
                        radian,
                        axis1,
                        axis2,
                        elementsCountAround,
                        startRadians=0.0):
    '''
    Create ellipse points centred at cx, from axis1 around through axis2.
    Assumes axis1 and axis2 are orthogonal.
    :param cx: centre
    :param radian: Part of ellipse to be created based on radian (will be 2*math.pi for a complete ellipse).
    :param axis1: Vector from cx to inside at zero angle
    :param axis2: Vector from cx to inside at 90 degree angle.
    :param elementsCountAround: Number of elements around.
    :param startRadians: Angle from axis1 to start creating the ellipse.
    :return: lists px, pd1
    '''
    px = []
    pd1 = []
    magAxis1 = vector.magnitude(axis1)
    magAxis2 = vector.magnitude(axis2)
    totalEllipsePerimeter = getApproximateEllipsePerimeter(magAxis1, magAxis2)
    partOfEllipsePerimeter = radian * totalEllipsePerimeter / (2 * math.pi)
    elementLength = partOfEllipsePerimeter / elementsCountAround
    if radian != 2 * math.pi:
        elementsCountAround = elementsCountAround + 1

    unitSideAxis1 = vector.normalise(axis1)
    unitSideAxis2 = vector.normalise(axis2)
    for n in range(elementsCountAround):
        angle = startRadians
        arcLength = n * elementLength
        newAngle = updateEllipseAngleByArcLength(magAxis1, magAxis2, angle,
                                                 arcLength)
        cosRadiansAround = math.cos(newAngle)
        sinRadiansAround = math.sin(newAngle)
        x = [
            cx[0] + cosRadiansAround * axis1[0] - sinRadiansAround * axis2[0],
            cx[1] + cosRadiansAround * axis1[1] + sinRadiansAround * axis2[1],
            cx[2] + cosRadiansAround * axis1[2] + sinRadiansAround * axis2[2]
        ]
        px.append(x)
        rd1 = [
            magAxis1 * (-sinRadiansAround * unitSideAxis1[c]) + magAxis2 *
            (cosRadiansAround * unitSideAxis2[c]) for c in range(3)
        ]
        rd1Norm = vector.normalise(rd1)
        pd1.append([elementLength * (rd1Norm[c]) for c in range(3)])

    return px, pd1
Esempio n. 7
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
Esempio n. 8
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
Esempio n. 9
0
 def getPlaneNormalVector(self):
     """
     Get plane normal vector
     :return: Unit normal vector.
     """
     p = self._plane
     return vector.normalise([p[0], p[1], p[2]])
Esempio n. 10
0
def normalToEllipse(v1, v2):
    """
    Find unit normal vector of an ellipse using two vectors in the ellipse. The direction is v1xv2
    :param v1: vector 1.
    :param v2: vector 2.
    :return:
    """
    nte = vector.normalise(vector.crossproduct3(v1, v2))
    return nte
Esempio n. 11
0
def computeNextCentre(centre, arcLength, axis):
    """
    compute next centre coordinate
    :param axis:
    :param arcLength: the length to go forward.
    :param centre: the start centre.
    :return: next centre coordinates.
    """
    centre = [
        centre[c] + arcLength * vector.normalise(axis)[c] for c in range(3)
    ]
    return centre
Esempio 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
Esempio n. 13
0
def interpolateNodesCubicHermite(cache, coordinates, xi, normal_scale, \
        node1, derivative1, scale1, cross_derivative1, cross_scale1, \
        node2, derivative2, scale2, cross_derivative2, cross_scale2):
    """
    Interpolates position and first derivative with cubic Hermite basis.
    Interpolates cross derivative linearly.
    :param cache: Field cache to evaluate in.
    :param coordinates: Coordinates field.
    :param xi: Element coordinate to interpolate at.
    :param normal_scale: Magnitude of normal derivative to return.
    :param node1, node2: Start and end nodes.
    :param derivative1, derivative2: Node value label for derivatives.
    :param scale1, scale2: Real value scaling derivatives, to reverse if needed.
    :param cross_derivative1, cross_derivative2: Node value label for cross derivatives.
    :param cross_scale1, cross_scale2: Real value scaling cross_derivatives, to reverse if needed.
    :return: x, dx_ds, dx_ds_cross, dx_ds_normal
    """
    cache.setNode(node1)
    result, v1 = coordinates.getNodeParameters(cache, -1,
                                               Node.VALUE_LABEL_VALUE, 1, 3)
    result, d1 = coordinates.getNodeParameters(cache, -1, derivative1, 1, 3)
    result, d1c = coordinates.getNodeParameters(cache, -1, cross_derivative1,
                                                1, 3)
    d1 = [scale1 * d for d in d1]
    d1c = [cross_scale1 * d for d in d1c]
    cache.setNode(node2)
    result, v2 = coordinates.getNodeParameters(cache, -1,
                                               Node.VALUE_LABEL_VALUE, 1, 3)
    result, d2 = coordinates.getNodeParameters(cache, -1, derivative2, 1, 3)
    result, d2c = coordinates.getNodeParameters(cache, -1, cross_derivative2,
                                                1, 3)
    d2 = [scale2 * d for d in d2]
    d2c = [cross_scale2 * d for d in d2c]

    arcLength = interp.computeCubicHermiteArcLength(v1, d1, v2, d2, True)
    mag = arcLength / vector.magnitude(d1)
    d1 = [mag * d for d in d1]
    mag = arcLength / vector.magnitude(d2)
    d2 = [mag * d for d in d2]

    xr = 1.0 - xi
    x = interp.interpolateCubicHermite(v1, d1, v2, d2, xi)
    dx_ds = interp.interpolateCubicHermiteDerivative(v1, d1, v2, d2, xi)
    scale = min(xi, xr)
    dx_ds = [scale * d for d in dx_ds]
    dx_ds_cross = [(xr * d1c[c] + xi * d2c[c]) for c in range(3)]

    radialVector = vector.normalise(vector.crossproduct3(dx_ds_cross, dx_ds))
    dx_ds_normal = [normal_scale * d for d in radialVector]

    return x, dx_ds, dx_ds_cross, dx_ds_normal
Esempio n. 14
0
    def generateBase1DMesh(self):
        """
        Generate nodes around the perimeter of the ellipse.
        """
        nx, nd1 = createEllipsePerimeter(self.centre, self.majorAxis,
                                         self.minorAxis,
                                         self.elementsCountAround,
                                         self.majorRadius)
        nte = normalToEllipse(self.majorAxis, self.minorAxis)

        tbx, tbd1, tbd2, tbd3 = [], [], [], []
        for n in range(self.elementsCountAround + 1):
            tbx.append(nx[n])
            tbd1.append(nd1[n])
            tbd2.append(nte)
            tbd3.append(vector.normalise(vector.crossproduct3(tbd1[n], nte)))

        self.setRimNodes(tbx, tbd1, tbd2, tbd3)
Esempio n. 15
0
    def generateBase1DMesh(self, rx):
        """
        Generate nodes around the perimeter of the ellipse.
        """
        btx = self.px
        btd1 = self.pd1
        btd2 = self.pd2
        btd3 = self.pd3

        ratio = rx / self.elementsCountAcrossShell if self.elementsCountAcrossShell > 0 else 0
        majorAxis = [
            d * (1 - ratio * (1 - self.coreMajorRadius / self.majorRadius))
            for d in self.majorAxis
        ]
        minorAxis = [
            d * (1 - ratio * (1 - self.coreMinorRadius / self.minorRadius))
            for d in self.minorAxis
        ]
        majorRadius = vector.magnitude(majorAxis)

        nx, nd1 = createEllipsePerimeter(self.centre, majorAxis, minorAxis,
                                         self.elementsCountAround, majorRadius)
        nte = normalToEllipse(self.majorAxis, self.minorAxis)

        tbx, tbd1, tbd2, tbd3 = [], [], [], []
        for n in range(self.elementsCountAround + 1):
            tbx.append(nx[n])
            tbd1.append(nd1[n])
            tbd2.append(nte)
            tbd3.append(vector.normalise(vector.crossproduct3(tbd1[n], nte)))

        for n in range(self.elementsCountAround + 1):
            n1, n2 = self.__shield.convertRimIndex(n, rx)
            btx[n2][n1] = tbx[n]
            btd1[n2][n1] = tbd1[n]
            btd2[n2][n1] = tbd2[n]
            btd3[n2][n1] = tbd3[n]
Esempio n. 16
0
def warpSegmentPoints(xList, d1List, d2List, segmentAxis, segmentLength, sx,
                      sd1, sd2, elementsCountAround, elementsCountAlongSegment,
                      nSegment, faceMidPointZ):
    """
    Warps points in segment to account for bending and twisting
    along central path defined by nodes sx and derivatives sd1 and sd2.
    :param xList: coordinates of segment points.
    :param d1List: derivatives around axis of segment.
    :param d2List: derivatives along axis of segment.
    :param segmentAxis: axis perpendicular to segment plane.
    :param sx: coordinates of points on central path.
    :param sd1: derivatives of points along central path.
    :param sd2: derivatives representing cross axes.
    :param elementsCountAround: Number of elements around segment.
    :param elementsCountAlongSegment: Number of elements along segment.
    :param nSegment: Segment index along central path.
    :param faceMidPointZ: z-coordinate of midpoint for each element
    groups along the segment.
    :return coordinates and derivatives of warped points.
    """
    xWarpedList = []
    d1WarpedList = []
    d2WarpedList = []
    smoothd2WarpedList = []
    d3WarpedUnitList = []

    for nAlongSegment in range(elementsCountAlongSegment + 1):
        n2 = elementsCountAlongSegment * nSegment + nAlongSegment
        xElementAlongSegment = xList[elementsCountAround *
                                     nAlongSegment:elementsCountAround *
                                     (nAlongSegment + 1)]
        d1ElementAlongSegment = d1List[elementsCountAround *
                                       nAlongSegment:elementsCountAround *
                                       (nAlongSegment + 1)]
        d2ElementAlongSegment = d2List[elementsCountAround *
                                       nAlongSegment:elementsCountAround *
                                       (nAlongSegment + 1)]
        xMid = [0.0, 0.0, faceMidPointZ[nAlongSegment]]

        # Rotate to align segment axis with tangent of central line
        unitTangent = vector.normalise(sd1[n2])
        cp = vector.crossproduct3(segmentAxis, unitTangent)
        dp = vector.dotproduct(segmentAxis, unitTangent)
        if vector.magnitude(
                cp) > 0.0:  # path tangent not parallel to segment axis
            axisRot = vector.normalise(cp)
            thetaRot = math.acos(vector.dotproduct(segmentAxis, unitTangent))
            rotFrame = matrix.getRotationMatrixFromAxisAngle(axisRot, thetaRot)
            midRot = [
                rotFrame[j][0] * xMid[0] + rotFrame[j][1] * xMid[1] +
                rotFrame[j][2] * xMid[2] for j in range(3)
            ]
        else:  # path tangent parallel to segment axis (z-axis)
            if dp == -1.0:  # path tangent opposite direction to segment axis
                thetaRot = math.pi
                axisRot = [1.0, 0, 0]
                rotFrame = matrix.getRotationMatrixFromAxisAngle(
                    axisRot, thetaRot)
                midRot = [
                    rotFrame[j][0] * xMid[0] + rotFrame[j][1] * xMid[1] +
                    rotFrame[j][2] * xMid[2] for j in range(3)
                ]
            else:  # segment axis in same direction as unit tangent
                midRot = xMid
        translateMatrix = [sx[n2][j] - midRot[j] for j in range(3)]

        for n1 in range(elementsCountAround):
            x = xElementAlongSegment[n1]
            d1 = d1ElementAlongSegment[n1]
            d2 = d2ElementAlongSegment[n1]

            if vector.magnitude(
                    cp) > 0.0:  # path tangent not parallel to segment axis
                xRot1 = [
                    rotFrame[j][0] * x[0] + rotFrame[j][1] * x[1] +
                    rotFrame[j][2] * x[2] for j in range(3)
                ]
                d1Rot1 = [
                    rotFrame[j][0] * d1[0] + rotFrame[j][1] * d1[1] +
                    rotFrame[j][2] * d1[2] for j in range(3)
                ]
                d2Rot1 = [
                    rotFrame[j][0] * d2[0] + rotFrame[j][1] * d2[1] +
                    rotFrame[j][2] * d2[2] for j in range(3)
                ]

                if n1 == 0:
                    # Project sd2 onto plane normal to sd1
                    v = sd2[n2]
                    pt = [midRot[j] + sd2[n2][j] for j in range(3)]
                    dist = vector.dotproduct(v, unitTangent)
                    ptOnPlane = [
                        pt[j] - dist * unitTangent[j] for j in range(3)
                    ]
                    newVector = [ptOnPlane[j] - midRot[j] for j in range(3)]
                    # Rotate first point to align with planar projection of sd2
                    firstVector = vector.normalise(
                        [xRot1[j] - midRot[j] for j in range(3)])
                    thetaRot2 = math.acos(
                        vector.dotproduct(vector.normalise(newVector),
                                          firstVector))
                    cp2 = vector.crossproduct3(vector.normalise(newVector),
                                               firstVector)
                    if vector.magnitude(cp2) > 0.0:
                        cp2 = vector.normalise(cp2)
                        signThetaRot2 = vector.dotproduct(unitTangent, cp2)
                        axisRot2 = unitTangent
                        rotFrame2 = matrix.getRotationMatrixFromAxisAngle(
                            axisRot2, -signThetaRot2 * thetaRot2)
                    else:
                        rotFrame2 = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]

            else:  # path tangent parallel to segment axis
                xRot1 = [
                    rotFrame[j][0] * x[0] + rotFrame[j][1] * x[1] +
                    rotFrame[j][2] * x[2] for j in range(3)
                ] if dp == -1.0 else x
                d1Rot1 = [
                    rotFrame[j][0] * d1[0] + rotFrame[j][1] * d1[1] +
                    rotFrame[j][2] * d1[2] for j in range(3)
                ] if dp == -1.0 else d1
                d2Rot1 = [
                    rotFrame[j][0] * d2[0] + rotFrame[j][1] * d2[1] +
                    rotFrame[j][2] * d2[2] for j in range(3)
                ] if dp == -1.0 else d2

                # Rotate to align start of elementsAround with sd2
                if n1 == 0:
                    v = vector.normalise(sd2[n2])
                    startVector = vector.normalise(
                        [xRot1[j] - midRot[j] for j in range(3)])
                    axisRot2 = unitTangent
                    thetaRot2 = dp * -math.acos(
                        vector.dotproduct(v, startVector))
                    rotFrame2 = matrix.getRotationMatrixFromAxisAngle(
                        axisRot2, thetaRot2)

            xRot2 = [
                rotFrame2[j][0] * xRot1[0] + rotFrame2[j][1] * xRot1[1] +
                rotFrame2[j][2] * xRot1[2] for j in range(3)
            ]
            d1Rot2 = [
                rotFrame2[j][0] * d1Rot1[0] + rotFrame2[j][1] * d1Rot1[1] +
                rotFrame2[j][2] * d1Rot1[2] for j in range(3)
            ]
            d2Rot2 = [
                rotFrame2[j][0] * d2Rot1[0] + rotFrame2[j][1] * d2Rot1[1] +
                rotFrame2[j][2] * d2Rot1[2] for j in range(3)
            ]
            xTranslate = [xRot2[j] + translateMatrix[j] for j in range(3)]

            xWarpedList.append(xTranslate)
            d1WarpedList.append(d1Rot2)
            d2WarpedList.append(d2Rot2)

    # Smooth d2 for segment
    smoothd2Raw = []
    for n1 in range(elementsCountAround):
        nx = []
        nd2 = []
        for n2 in range(elementsCountAlongSegment + 1):
            n = n2 * elementsCountAround + n1
            nx.append(xWarpedList[n])
            nd2.append(d2WarpedList[n])
        smoothd2 = interp.smoothCubicHermiteDerivativesLine(
            nx, nd2, fixStartDerivative=True, fixEndDerivative=True)
        smoothd2Raw.append(smoothd2)

    # Re-arrange smoothd2
    for n2 in range(elementsCountAlongSegment + 1):
        for n1 in range(elementsCountAround):
            smoothd2WarpedList.append(smoothd2Raw[n1][n2])

    # Calculate unit d3
    for n in range(len(xWarpedList)):
        d3Unit = vector.normalise(
            vector.crossproduct3(vector.normalise(d1WarpedList[n]),
                                 vector.normalise(smoothd2WarpedList[n])))
        d3WarpedUnitList.append(d3Unit)

    return xWarpedList, d1WarpedList, smoothd2WarpedList, d3WarpedUnitList
    def generateBaseMesh(cls, region, options):
        """
        Generate the base tricubic Hermite mesh. See also generateMesh().
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: annotationGroups
        """
        centralPath = options['Central path']
        elementsCountAround = options['Number of elements around']
        elementsCountAlong = options['Number of elements along']
        elementsCountThroughWall = options['Number of elements through wall']
        wallThickness = options['Wall thickness']
        mucosaRelThickness = options['Mucosa relative thickness']
        submucosaRelThickness = options['Submucosa relative thickness']
        circularRelThickness = options[
            'Circular muscle layer relative thickness']
        longitudinalRelThickness = options[
            'Longitudinal muscle layer relative thickness']
        useCrossDerivatives = options['Use cross derivatives']
        useCubicHermiteThroughWall = not (options['Use linear through wall'])

        firstNodeIdentifier = 1
        firstElementIdentifier = 1

        # Central path
        esophagusTermsAlong = [
            None, 'cervical part of esophagus', 'thoracic part of esophagus',
            'abdominal part of esophagus'
        ]
        arcLengthOfGroupsAlong = []
        for i in range(len(esophagusTermsAlong)):
            tmpRegion = region.createRegion()
            centralPath.generate(tmpRegion)
            cxGroup, cd1Group, cd2Group, cd3Group, cd12Group, cd13Group = \
                extractPathParametersFromRegion(tmpRegion, [Node.VALUE_LABEL_VALUE, Node.VALUE_LABEL_D_DS1,
                                                            Node.VALUE_LABEL_D_DS2, Node.VALUE_LABEL_D_DS3,
                                                            Node.VALUE_LABEL_D2_DS1DS2, Node.VALUE_LABEL_D2_DS1DS3],
                                                groupName=esophagusTermsAlong[i])
            arcLength = 0.0
            for e in range(len(cxGroup) - 1):
                arcLength += interp.getCubicHermiteArcLength(
                    cxGroup[e], cd1Group[e], cxGroup[e + 1], cd1Group[e + 1])
            arcLengthOfGroupsAlong.append(arcLength)

            if i == 0:
                cx = cxGroup
                cd1 = cd1Group
                cd2 = cd2Group
                cd3 = cd3Group
                cd12 = cd12Group
                cd13 = cd13Group

            del tmpRegion

        # Sample central path
        sx, sd1, se, sxi, ssf = interp.sampleCubicHermiteCurves(
            cx, cd1, elementsCountAlong)
        sd2, sd12 = interp.interpolateSampleCubicHermite(
            cd2, cd12, se, sxi, ssf)
        sd3, sd13 = interp.interpolateSampleCubicHermite(
            cd3, cd13, se, sxi, ssf)

        centralPathLength = arcLengthOfGroupsAlong[0]
        elementAlongLength = centralPathLength / elementsCountAlong

        elementsCountAlongGroups = []
        groupLength = 0.0
        e = 0
        elementsCount = 1
        length = elementAlongLength
        for i in range(1, len(esophagusTermsAlong)):
            groupLength += arcLengthOfGroupsAlong[i]
            if e == elementsCountAlong - 2:
                elementsCount += 1
                elementsCountAlongGroups.append(elementsCount)
            else:
                while length < groupLength:
                    elementsCount += 1
                    e += 1
                    length += elementAlongLength

                # check which end is grouplength closer to
                distToUpperEnd = abs(length - groupLength)
                distToLowerEnd = abs(groupLength -
                                     (length - elementsCountAlong))
                if distToLowerEnd < distToUpperEnd:
                    elementsCount -= 1
                    elementsCountAlongGroups.append(elementsCount)
                    e -= 1
                    length -= elementAlongLength
                else:
                    elementsCountAlongGroups.append(elementsCount)
            elementsCount = 0

        majorRadiusElementList = sd2
        minorRadiusElementList = sd3

        # Create annotation groups along esophagus
        esophagusGroup = AnnotationGroup(region,
                                         get_esophagus_term("esophagus"))
        cervicalGroup = AnnotationGroup(
            region, get_esophagus_term("cervical part of esophagus"))
        thoracicGroup = AnnotationGroup(
            region, get_esophagus_term("thoracic part of esophagus"))
        abdominalGroup = AnnotationGroup(
            region, get_esophagus_term("abdominal part of esophagus"))

        annotationGroupAlong = [[esophagusGroup, cervicalGroup],
                                [esophagusGroup, thoracicGroup],
                                [esophagusGroup, abdominalGroup]]

        annotationGroupsAlong = []
        for i in range(len(elementsCountAlongGroups)):
            elementsCount = elementsCountAlongGroups[i]
            for n in range(elementsCount):
                annotationGroupsAlong.append(annotationGroupAlong[i])

        annotationGroupsAround = []
        for i in range(elementsCountAround):
            annotationGroupsAround.append([])

        # Groups through wall
        longitudinalMuscleGroup = AnnotationGroup(
            region,
            get_esophagus_term("esophagus smooth muscle longitudinal layer"))
        circularMuscleGroup = AnnotationGroup(
            region,
            get_esophagus_term("esophagus smooth muscle circular layer"))
        submucosaGroup = AnnotationGroup(
            region, get_esophagus_term("submucosa of esophagus"))
        mucosaGroup = AnnotationGroup(region,
                                      get_esophagus_term("esophagus mucosa"))

        if elementsCountThroughWall == 1:
            relativeThicknessList = [1.0]
            annotationGroupsThroughWall = [[]]
        else:
            relativeThicknessList = [
                mucosaRelThickness, submucosaRelThickness,
                circularRelThickness, longitudinalRelThickness
            ]
            annotationGroupsThroughWall = [[mucosaGroup], [submucosaGroup],
                                           [circularMuscleGroup],
                                           [longitudinalMuscleGroup]]

        xToSample = []
        d1ToSample = []
        for n2 in range(elementsCountAlong + 1):
            # Create inner points
            cx = [0.0, 0.0, elementAlongLength * n2]
            axis1 = [vector.magnitude(majorRadiusElementList[n2]), 0.0, 0.0]
            axis2 = [0.0, vector.magnitude(minorRadiusElementList[n2]), 0.0]
            xInner, d1Inner = geometry.createEllipsePoints(cx,
                                                           2 * math.pi,
                                                           axis1,
                                                           axis2,
                                                           elementsCountAround,
                                                           startRadians=0.0)
            xToSample += xInner
            d1ToSample += d1Inner

        d2ToSample = [[0.0, 0.0, elementAlongLength]
                      ] * (elementsCountAround * (elementsCountAlong + 1))

        # Sample along length
        xInnerRaw = []
        d2InnerRaw = []
        xToWarp = []
        d1ToWarp = []
        d2ToWarp = []
        flatWidthList = []
        xiList = []

        for n1 in range(elementsCountAround):
            xForSamplingAlong = []
            d2ForSamplingAlong = []
            for n2 in range(elementsCountAlong + 1):
                idx = n2 * elementsCountAround + n1
                xForSamplingAlong.append(xToSample[idx])
                d2ForSamplingAlong.append(d2ToSample[idx])
            xSampled, d2Sampled = interp.sampleCubicHermiteCurves(
                xForSamplingAlong,
                d2ForSamplingAlong,
                elementsCountAlong,
                arcLengthDerivatives=True)[0:2]
            xInnerRaw.append(xSampled)
            d2InnerRaw.append(d2Sampled)

        # Re-arrange sample order & calculate dx_ds1 and dx_ds3 from dx_ds2
        for n2 in range(elementsCountAlong + 1):
            xAround = []
            d2Around = []

            for n1 in range(elementsCountAround):
                x = xInnerRaw[n1][n2]
                d2 = d2InnerRaw[n1][n2]
                xAround.append(x)
                d2Around.append(d2)

            d1Around = []
            for n1 in range(elementsCountAround):
                v1 = xAround[n1]
                v2 = xAround[(n1 + 1) % elementsCountAround]
                d1 = d2 = [v2[c] - v1[c] for c in range(3)]
                arcLengthAround = interp.computeCubicHermiteArcLength(
                    v1, d1, v2, d2, True)
                dx_ds1 = [c * arcLengthAround for c in vector.normalise(d1)]
                d1Around.append(dx_ds1)
            d1Smoothed = interp.smoothCubicHermiteDerivativesLoop(
                xAround, d1Around)

            xToWarp += xAround
            d1ToWarp += d1Smoothed
            d2ToWarp += d2Around

            # Flat width and xi
            flatWidth = 0.0
            xiFace = []
            for n1 in range(elementsCountAround):
                v1 = xAround[n1]
                d1 = d1Smoothed[n1]
                v2 = xAround[(n1 + 1) % elementsCountAround]
                d2 = d1Smoothed[(n1 + 1) % elementsCountAround]
                flatWidth += interp.getCubicHermiteArcLength(v1, d1, v2, d2)
            flatWidthList.append(flatWidth)

            for n1 in range(elementsCountAround + 1):
                xi = 1.0 / elementsCountAround * n1
                xiFace.append(xi)
            xiList.append(xiFace)

        # Project reference point for warping onto central path
        sxRefList, sd1RefList, sd2ProjectedListRef, zRefList = \
            tubemesh.getPlaneProjectionOnCentralPath(xToWarp, elementsCountAround, elementsCountAlong,
                                                     centralPathLength, sx, sd1, sd2, sd12)

        # Warp points
        segmentAxis = [0.0, 0.0, 1.0]
        closedProximalEnd = False

        innerRadiusAlong = []
        for n2 in range(elementsCountAlong + 1):
            firstNodeAlong = xToWarp[n2 * elementsCountAround]
            midptSegmentAxis = [0.0, 0.0, elementAlongLength * n2]
            radius = vector.magnitude(firstNodeAlong[c] - midptSegmentAxis[c]
                                      for c in range(3))
            innerRadiusAlong.append(radius)

        xWarpedList, d1WarpedList, d2WarpedList, d3WarpedUnitList = \
            tubemesh.warpSegmentPoints(xToWarp, d1ToWarp, d2ToWarp, segmentAxis, sxRefList, sd1RefList,
                                       sd2ProjectedListRef, elementsCountAround, elementsCountAlong,
                                       zRefList, innerRadiusAlong, closedProximalEnd)

        # Create coordinates and derivatives
        transitElementList = [0] * elementsCountAround
        xList, d1List, d2List, d3List, curvatureList = \
            tubemesh.getCoordinatesFromInner(xWarpedList, d1WarpedList, d2WarpedList, d3WarpedUnitList,
                                             [wallThickness]*(elementsCountAlong+1), relativeThicknessList,
                                             elementsCountAround, elementsCountAlong, elementsCountThroughWall,
                                             transitElementList)

        # Create flat coordinates
        xFlat, d1Flat, d2Flat = tubemesh.createFlatCoordinates(
            xiList, flatWidthList, length, wallThickness,
            relativeThicknessList, elementsCountAround, elementsCountAlong,
            elementsCountThroughWall, transitElementList)

        # Create nodes and elements
        xOrgan = []
        d1Organ = []
        d2Organ = []
        nodeIdentifier, elementIdentifier, annotationGroups = \
            tubemesh.createNodesAndElements(region, xList, d1List, d2List, d3List, xFlat, d1Flat, d2Flat,
                                            xOrgan, d1Organ, d2Organ, None, elementsCountAround, elementsCountAlong,
                                            elementsCountThroughWall, annotationGroupsAround, annotationGroupsAlong,
                                            annotationGroupsThroughWall, firstNodeIdentifier, firstElementIdentifier,
                                            useCubicHermiteThroughWall, useCrossDerivatives, closedProximalEnd)

        # annotation fiducial points
        fm = region.getFieldmodule()
        fm.beginChange()
        mesh = fm.findMeshByDimension(3)
        cache = fm.createFieldcache()

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

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

        markerNames = [
            "proximodorsal midpoint on serosa of upper esophageal sphincter",
            "proximoventral midpoint on serosa of upper esophageal sphincter",
            "distal point of lower esophageal sphincter serosa on the greater curvature of stomach",
            "distal point of lower esophageal sphincter serosa on the lesser curvature of stomach"
        ]

        totalElements = elementIdentifier
        radPerElementAround = math.pi * 2.0 / elementsCountAround
        elementAroundHalfPi = int(0.25 * elementsCountAround)
        xi1HalfPi = (math.pi * 0.5 - radPerElementAround *
                     elementAroundHalfPi) / radPerElementAround
        elementAroundPi = int(0.5 * elementsCountAround)
        xi1Pi = (math.pi -
                 radPerElementAround * elementAroundPi) / radPerElementAround

        markerElementIdentifiers = [
            elementsCountAround * elementsCountThroughWall -
            elementAroundHalfPi, elementAroundHalfPi + 1 +
            elementsCountAround * (elementsCountThroughWall - 1),
            totalElements - elementsCountAround,
            totalElements - elementsCountAround + elementAroundPi
        ]

        markerXis = [[1.0 - xi1HalfPi, 0.0, 1.0], [xi1HalfPi, 0.0, 1.0],
                     [0.0, 1.0, 1.0], [xi1Pi, 1.0, 1.0]]

        for n in range(len(markerNames)):
            markerGroup = findOrCreateAnnotationGroupForTerm(
                annotationGroups, region, get_esophagus_term(markerNames[n]))
            markerElement = mesh.findElementByIdentifier(
                markerElementIdentifiers[n])
            markerXi = markerXis[n]
            cache.setMeshLocation(markerElement, markerXi)
            markerPoint = markerPoints.createNode(nodeIdentifier,
                                                  markerTemplateInternal)
            nodeIdentifier += 1
            cache.setNode(markerPoint)
            markerName.assignString(cache, markerGroup.getName())
            markerLocation.assignMeshLocation(cache, markerElement, markerXi)
            for group in [esophagusGroup, markerGroup]:
                group.getNodesetGroup(nodes).addNode(markerPoint)

        fm.endChange()

        return annotationGroups
    def replaceElementWithInlet4(self, element, startElementId, nodetemplate, startNodeId, tubeLength, innerDiameter, wallThickness):
        '''
        Replace element with 4 element X-layout tube inlet.
        Inlet axis is at given length from centre of xi3=0 face, oriented with dx/dxi1.
        8 new nodes are created.
        '''
        fm = self._mesh.getFieldmodule()
        nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
        fm.beginChange()
        cache = fm.createFieldcache()
        diff1 = self._mesh.getChartDifferentialoperator(1, 1)
        diff2 = self._mesh.getChartDifferentialoperator(1, 2)
        coordinates = getOrCreateCoordinateField(fm)
        cache.setMeshLocation(element, [0.5, 0.5, 1.0])
        result, fc = coordinates.evaluateReal(cache, 3)
        resulta, a = coordinates.evaluateDerivative(diff1, cache, 3)
        resultb, b = coordinates.evaluateDerivative(diff2, cache, 3)
        n = vector.normalise(vector.crossproduct3(a, b))
        #print(resulta, 'a =', a, ',', resultb, ' b=', b, ' fc=', fc, ' n=',n)
        ic = [ (fc[i] + tubeLength*n[i]) for i in range(3) ]
        na = vector.normalise(a)
        nb = vector.normalise(b)
        a = vector.normalise([ -(na[i] + nb[i]) for i in range(3) ])
        b = vector.normalise(vector.crossproduct3(a, n))

        zero = [ 0.0, 0.0, 0.0 ]
        nodeIdentifier = startNodeId
        elementsCountAround = 4
        radiansPerElementAround = math.pi*2.0/elementsCountAround
        for n3 in range(2):
            radius = innerDiameter*0.5 + n3*wallThickness
            for n1 in range(elementsCountAround):
                radiansAround = n1*radiansPerElementAround
                cosRadiansAround = math.cos(radiansAround)
                sinRadiansAround = math.sin(radiansAround)
                x = [ (ic[i] + radius*(cosRadiansAround*a[i] + sinRadiansAround*b[i])) for i in range(3) ]
                dx_ds1 = [ radiansPerElementAround*radius*(-sinRadiansAround*a[i] + cosRadiansAround*b[i]) for i in range(3) ]
                dx_ds2 = [ -tubeLength*c for c in n ]
                dx_ds3 = [ wallThickness*(cosRadiansAround*a[i] + sinRadiansAround*b[i]) for i in range(3) ]
                node = nodes.createNode(nodeIdentifier, nodetemplate)
                cache.setNode(node)
                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, x)
                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, dx_ds1)
                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, dx_ds2)
                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, dx_ds3)
                if self._useCrossDerivatives:
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D2_DS1DS2, 1, zero)
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D2_DS1DS3, 1, zero)
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D2_DS2DS3, 1, zero)
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D3_DS1DS2DS3, 1, zero)
                nodeIdentifier = nodeIdentifier + 1

        eft0 = element.getElementfieldtemplate(coordinates, -1)
        nids0 = getElementNodeIdentifiers(element, eft0)
        orig_nids = [ nids0[0], nids0[2], nids0[3], nids0[1], nids0[4], nids0[6], nids0[7], nids0[5] ]

        elementIdentifier = startElementId
        elementtemplate1 = self._mesh.createElementtemplate()
        elementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE)
        for e in range(4):
            eft1 = self.createEftNoCrossDerivatives()
            setEftScaleFactorIds(eft1, [1], [])
            if e == 0:
                remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS1, [1]), (Node.VALUE_LABEL_D_DS2, [1]) ])
                remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS2, []) ])
                remapEftNodeValueLabel(eft1, [ 4, 8 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS1, [1]), (Node.VALUE_LABEL_D_DS2, []) ])
                remapEftNodeValueLabel(eft1, [ 4, 8 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS2, []) ])
            elif e == 1:
                remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS1, [1]), (Node.VALUE_LABEL_D_DS2, []) ])
                remapEftNodeValueLabel(eft1, [ 4, 8 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS2, []) ])
            elif e == 2:
                remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS2, []) ])
                remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS2, [1]) ])
                remapEftNodeValueLabel(eft1, [ 4, 8 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS2, [1]) ])
                remapEftNodeValueLabel(eft1, [ 4, 8 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS2, [1]) ])
            elif e == 3:
                remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS1, []), (Node.VALUE_LABEL_D_DS2, [1]) ])
                remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, [1]) ])
                remapEftNodeValueLabel(eft1, [ 4, 8 ], Node.VALUE_LABEL_D_DS2, [ (Node.VALUE_LABEL_D_DS1, [1]), (Node.VALUE_LABEL_D_DS2, [1]) ])
                remapEftNodeValueLabel(eft1, [ 4, 8 ], Node.VALUE_LABEL_D_DS1, [ (Node.VALUE_LABEL_D_DS1, [1]) ])
            ea = e
            eb = (e + 1) % 4
            ec = ea + 4
            ed = eb + 4
            nids = [
                startNodeId + ea, startNodeId + eb, orig_nids[ea], orig_nids[eb],
                startNodeId + ec, startNodeId + ed, orig_nids[ec], orig_nids[ed]
            ]
            elementtemplate1.defineField(coordinates, -1, eft1)
            element = self._mesh.createElement(elementIdentifier, elementtemplate1)
            result2 = element.setNodesByIdentifier(eft1, nids)
            if eft1.getNumberOfLocalScaleFactors() == 1:
                result3 = element.setScaleFactors(eft1, [ -1.0 ])
            else:
                result3 = 7
            #print('create element in', element.isValid(), elementIdentifier, result2, result3, nids)
            elementIdentifier += 1

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

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

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

        cache = fm.createFieldcache()

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

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

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

        cubicArcLengthList = [0.0] * (elementsCountUp + 1)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        mesh = fm.findMeshByDimension(3)

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

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

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

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

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

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

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

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

        elementIdentifier = 1

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

        for e3 in range(elementsCountRadial):

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

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

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

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

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

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

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

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

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

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

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

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

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

        fm.endChange()
        return []
Esempio n. 20
0
    def getTriplePoints(self, n3):
        '''
        Compute coordinates and derivatives of points where 3 square elements merge.
        :param n3: Index of through-wall coordinates to use.
        '''
        n1a = self.elementsCountRim
        n1b = n1a + 1
        n1c = n1a + 2
        m1a = self.elementsCountAcross - self.elementsCountRim
        m1b = m1a - 1
        m1c = m1a - 2
        n2a = self.elementsCountRim
        n2b = n2a + 1
        n2c = n2a + 2
        # left
        ltx = []

        if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][n1c], self.px[n3][n2c][n1b]],
                [[(-self.pd1[n3][n2a][n1c][c] - self.pd3[n3][n2a][n1c][c])
                  for c in range(3)], self.pd1[n3][n2c][n1b]],
                2,
                arcLengthDerivatives=True)[0:2]
            ltx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][n1b], self.px[n3][n2c][n1c]],
                [[-self.pd3[n3][n2a][n1b][c] for c in range(3)],
                 [(self.pd1[n3][n2c][n1c][c] + self.pd3[n3][n2c][n1c][c])
                  for c in range(3)]],
                2,
                arcLengthDerivatives=True)[0:2]
            ltx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2c][n1a], self.px[n3][n2b][n1c]],
                [[(self.pd1[n3][n2c][n1a][c] - self.pd3[n3][n2c][n1a][c])
                  for c in range(3)], self.pd3[n3][n2b][n1c]],
                2,
                arcLengthDerivatives=True)[0:2]
            ltx.append(tx[1])
        elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][n1c], self.px[n3][n2c][n1b]],
                [[(-self.pd1[n3][n2a][n1c][c] + self.pd2[n3][n2a][n1c][c])
                  for c in range(3)], self.pd2[n3][n2c][n1b]],
                2,
                arcLengthDerivatives=True)[0:2]
            ltx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][n1b], self.px[n3][n2c][n1c]], [
                    self.pd2[n3][n2a][n1b],
                    [(self.pd1[n3][n2c][n1c][c] + self.pd2[n3][n2c][n1c][c])
                     for c in range(3)]
                ],
                2,
                arcLengthDerivatives=True)[0:2]
            ltx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2c][n1a], self.px[n3][n2b][n1c]],
                [[(self.pd1[n3][n2c][n1a][c] - self.pd2[n3][n2c][n1a][c])
                  for c in range(3)], self.pd1[n3][n2b][n1c]],
                2,
                arcLengthDerivatives=True)[0:2]
            ltx.append(tx[1])
        #x = [ (ltx[0][c] + ltx[1][c] + ltx[2][c])/3.0 for c in range(3) ]
        x = [(ltx[0][c] + ltx[2][c]) / 2.0 for c in range(3)]
        if self.trackSurface:
            p = self.trackSurface.findNearestPosition(
                x,
                startPosition=self.trackSurface.createPositionProportion(
                    *(self.pProportions[n2b][n1c])))
            self.pProportions[n2b][n1b] = self.trackSurface.getProportion(p)
            x, sd1, sd2 = self.trackSurface.evaluateCoordinates(
                p, derivatives=True)
            d1, d2, d3 = calculate_surface_axes(sd1, sd2,
                                                vector.normalise(sd1))
            self.pd3[n3][n2b][n1b] = d3
        self.px[n3][n2b][n1b] = x
        if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
            self.pd3[n3][n2b][n1b] = [
                (self.px[n3][n2b][n1c][c] - self.px[n3][n2b][n1b][c])
                for c in range(3)
            ]
            self.pd1[n3][n2b][n1b] = [
                (self.px[n3][n2c][n1b][c] - self.px[n3][n2b][n1b][c])
                for c in range(3)
            ]
        elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
            self.pd1[n3][n2b][n1b] = [
                (self.px[n3][n2b][n1c][c] - self.px[n3][n2b][n1b][c])
                for c in range(3)
            ]
            self.pd2[n3][n2b][n1b] = [
                (self.px[n3][n2c][n1b][c] - self.px[n3][n2b][n1b][c])
                for c in range(3)
            ]
        if not self.trackSurface:
            if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
                self.pd2[n3][n2b][n1b] = vector.normalise(
                    vector.crossproduct3(self.pd3[n3][n2b][n1b],
                                         self.pd1[n3][n2b][n1b]))
            elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
                self.pd3[n3][n2b][n1b] = vector.normalise(
                    vector.crossproduct3(self.pd1[n3][n2b][n1b],
                                         self.pd2[n3][n2b][n1b]))
        # right
        rtx = []
        if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][m1c], self.px[n3][n2c][m1b]],
                [[(self.pd1[n3][n2a][m1c][c] - self.pd3[n3][n2a][m1c][c])
                  for c in range(3)], self.pd1[n3][n2c][m1b]],
                2,
                arcLengthDerivatives=True)[0:2]
            rtx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][m1b], self.px[n3][n2c][m1c]],
                [[-self.pd3[n3][n2a][m1b][c] for c in range(3)],
                 [(-self.pd3[n3][n2c][m1c][c] + self.pd1[n3][n2c][m1c][c])
                  for c in range(3)]],
                2,
                arcLengthDerivatives=True)[0:2]
            rtx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2c][m1a], self.px[n3][n2b][m1c]],
                [[(-self.pd1[n3][n2c][m1a][c] - self.pd3[n3][n2c][m1a][c])
                  for c in range(3)], [-d for d in self.pd3[n3][n2b][m1c]]],
                2,
                arcLengthDerivatives=True)[0:2]
            rtx.append(tx[1])
        elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][m1c], self.px[n3][n2c][m1b]],
                [[(self.pd1[n3][n2a][m1c][c] + self.pd2[n3][n2a][m1c][c])
                  for c in range(3)], self.pd2[n3][n2c][m1b]],
                2,
                arcLengthDerivatives=True)[0:2]
            rtx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][m1b], self.px[n3][n2c][m1c]], [
                    self.pd2[n3][n2a][m1b],
                    [(-self.pd1[n3][n2c][m1c][c] + self.pd2[n3][n2c][m1c][c])
                     for c in range(3)]
                ],
                2,
                arcLengthDerivatives=True)[0:2]
            rtx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2c][m1a], self.px[n3][n2b][m1c]],
                [[(-self.pd1[n3][n2c][m1a][c] - self.pd2[n3][n2c][m1a][c])
                  for c in range(3)], [-d for d in self.pd1[n3][n2b][m1c]]],
                2,
                arcLengthDerivatives=True)[0:2]
            rtx.append(tx[1])
        #x = [ (rtx[0][c] + rtx[1][c] + rtx[2][c])/3.0 for c in range(3) ]
        x = [(rtx[0][c] + rtx[2][c]) / 2.0 for c in range(3)]
        if self.trackSurface:
            p = self.trackSurface.findNearestPosition(
                x,
                startPosition=self.trackSurface.createPositionProportion(
                    *(self.pProportions[n2b][m1c])))
            self.pProportions[n2b][m1b] = self.trackSurface.getProportion(p)
            x, sd1, sd2 = self.trackSurface.evaluateCoordinates(
                p, derivatives=True)
            d1, d2, d3 = calculate_surface_axes(sd1, sd2,
                                                vector.normalise(sd1))
            self.pd3[n3][n2b][m1b] = d3
        self.px[n3][n2b][m1b] = x
        if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
            self.pd3[n3][n2b][m1b] = [
                (self.px[n3][n2b][m1b][c] - self.px[n3][n2b][m1c][c])
                for c in range(3)
            ]
            self.pd1[n3][n2b][m1b] = [
                (self.px[n3][n2c][m1b][c] - self.px[n3][n2b][m1b][c])
                for c in range(3)
            ]
        elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
            self.pd1[n3][n2b][m1b] = [
                (self.px[n3][n2b][m1b][c] - self.px[n3][n2b][m1c][c])
                for c in range(3)
            ]
            self.pd2[n3][n2b][m1b] = [
                (self.px[n3][n2c][m1b][c] - self.px[n3][n2b][m1b][c])
                for c in range(3)
            ]
        if not self.trackSurface:
            if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
                self.pd2[n3][n2b][m1b] = vector.normalise(
                    vector.crossproduct3(self.pd3[n3][n2b][m1b],
                                         self.pd1[n3][n2b][m1b]))
            elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
                self.pd3[n3][n2b][m1b] = vector.normalise(
                    vector.crossproduct3(self.pd1[n3][n2b][m1b],
                                         self.pd2[n3][n2b][m1b]))
Esempio n. 21
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()
Esempio n. 22
0
def warpSegmentPoints(xList, d1List, d2List, segmentAxis, sx, sd1, sd2,
                      elementsCountAround, elementsCountAlongSegment,
                      refPointZ, innerRadiusAlong, closedProximalEnd):
    """
    Warps points in segment to account for bending and twisting
    along central path defined by nodes sx and derivatives sd1 and sd2.
    :param xList: coordinates of segment points.
    :param d1List: derivatives around axis of segment.
    :param d2List: derivatives along axis of segment.
    :param segmentAxis: axis perpendicular to segment plane.
    :param sx: coordinates of points on central path.
    :param sd1: derivatives of points along central path.
    :param sd2: derivatives representing cross axes.
    :param elementsCountAround: Number of elements around segment.
    :param elementsCountAlongSegment: Number of elements along segment.
    :param refPointZ: z-coordinate of reference point for each element
    groups along the segment to be used for transformation.
    :param innerRadiusAlong: radius of segment along length.
    :param closedProximalEnd: True if proximal end of segment is a closed end.
    :return coordinates and derivatives of warped points.
    """

    xWarpedList = []
    d1WarpedList = []
    d2WarpedList = []
    d2WarpedListFinal = []
    d3WarpedUnitList = []

    for nAlongSegment in range(elementsCountAlongSegment + 1):
        xElementAlongSegment = xList[elementsCountAround *
                                     nAlongSegment:elementsCountAround *
                                     (nAlongSegment + 1)]
        d1ElementAlongSegment = d1List[elementsCountAround *
                                       nAlongSegment:elementsCountAround *
                                       (nAlongSegment + 1)]
        d2ElementAlongSegment = d2List[elementsCountAround *
                                       nAlongSegment:elementsCountAround *
                                       (nAlongSegment + 1)]

        centroid = [0.0, 0.0, refPointZ[nAlongSegment]]

        # Rotate to align segment axis with tangent of central line
        unitTangent = vector.normalise(sd1[nAlongSegment])
        cp = vector.crossproduct3(segmentAxis, unitTangent)
        dp = vector.dotproduct(segmentAxis, unitTangent)
        if vector.magnitude(
                cp) > 0.0:  # path tangent not parallel to segment axis
            axisRot = vector.normalise(cp)
            thetaRot = math.acos(vector.dotproduct(segmentAxis, unitTangent))
            rotFrame = matrix.getRotationMatrixFromAxisAngle(axisRot, thetaRot)
            centroidRot = [
                rotFrame[j][0] * centroid[0] + rotFrame[j][1] * centroid[1] +
                rotFrame[j][2] * centroid[2] for j in range(3)
            ]

        else:  # path tangent parallel to segment axis (z-axis)
            if dp == -1.0:  # path tangent opposite direction to segment axis
                thetaRot = math.pi
                axisRot = [1.0, 0, 0]
                rotFrame = matrix.getRotationMatrixFromAxisAngle(
                    axisRot, thetaRot)
                centroidRot = [
                    rotFrame[j][0] * centroid[0] +
                    rotFrame[j][1] * centroid[1] + rotFrame[j][2] * centroid[2]
                    for j in range(3)
                ]

            else:  # segment axis in same direction as unit tangent
                rotFrame = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
                centroidRot = centroid

        translateMatrix = [
            sx[nAlongSegment][j] - centroidRot[j] for j in range(3)
        ]

        for n1 in range(elementsCountAround):
            x = xElementAlongSegment[n1]
            d1 = d1ElementAlongSegment[n1]
            d2 = d2ElementAlongSegment[n1]

            if vector.magnitude(
                    cp) > 0.0:  # path tangent not parallel to segment axis
                xRot1 = [
                    rotFrame[j][0] * x[0] + rotFrame[j][1] * x[1] +
                    rotFrame[j][2] * x[2] for j in range(3)
                ]
                d1Rot1 = [
                    rotFrame[j][0] * d1[0] + rotFrame[j][1] * d1[1] +
                    rotFrame[j][2] * d1[2] for j in range(3)
                ]
                d2Rot1 = [
                    rotFrame[j][0] * d2[0] + rotFrame[j][1] * d2[1] +
                    rotFrame[j][2] * d2[2] for j in range(3)
                ]
                # xTranslate = [xRot1[j] + translateMatrix[j] for j in range(3)]

            else:  # path tangent parallel to segment axis
                xRot1 = [
                    rotFrame[j][0] * x[0] + rotFrame[j][1] * x[1] +
                    rotFrame[j][2] * x[2] for j in range(3)
                ] if dp == -1.0 else x
                d1Rot1 = [
                    rotFrame[j][0] * d1[0] + rotFrame[j][1] * d1[1] +
                    rotFrame[j][2] * d1[2] for j in range(3)
                ] if dp == -1.0 else d1
                d2Rot1 = [
                    rotFrame[j][0] * d2[0] + rotFrame[j][1] * d2[1] +
                    rotFrame[j][2] * d2[2] for j in range(3)
                ] if dp == -1.0 else d2
                # xTranslate = [xRot1[j] + translateMatrix[j] for j in range(3)]

            if n1 == 0:  # Find angle between xCentroidRot and first node in the face
                vectorToFirstNode = [
                    xRot1[c] - centroidRot[c] for c in range(3)
                ]
                if vector.magnitude(vectorToFirstNode) > 0.0:
                    cp = vector.crossproduct3(
                        vector.normalise(vectorToFirstNode),
                        vector.normalise(sd2[nAlongSegment]))
                    if vector.magnitude(cp) > 1e-7:
                        cp = vector.normalise(cp)
                        signThetaRot2 = vector.dotproduct(unitTangent, cp)
                        thetaRot2 = math.acos(
                            vector.dotproduct(
                                vector.normalise(vectorToFirstNode),
                                sd2[nAlongSegment]))
                        axisRot2 = unitTangent
                        rotFrame2 = matrix.getRotationMatrixFromAxisAngle(
                            axisRot2, signThetaRot2 * thetaRot2)
                    else:
                        rotFrame2 = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
                else:
                    rotFrame2 = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]

            xRot2 = [
                rotFrame2[j][0] * xRot1[0] + rotFrame2[j][1] * xRot1[1] +
                rotFrame2[j][2] * xRot1[2] for j in range(3)
            ]
            d1Rot2 = [
                rotFrame2[j][0] * d1Rot1[0] + rotFrame2[j][1] * d1Rot1[1] +
                rotFrame2[j][2] * d1Rot1[2] for j in range(3)
            ]
            d2Rot2 = [
                rotFrame2[j][0] * d2Rot1[0] + rotFrame2[j][1] * d2Rot1[1] +
                rotFrame2[j][2] * d2Rot1[2] for j in range(3)
            ]
            xTranslate = [xRot2[j] + translateMatrix[j] for j in range(3)]

            xWarpedList.append(xTranslate)
            d1WarpedList.append(d1Rot2)
            d2WarpedList.append(d2Rot2)

    # Scale d2 with curvature of central path
    d2WarpedListScaled = []
    vProjectedList = []
    for nAlongSegment in range(elementsCountAlongSegment + 1):
        for n1 in range(elementsCountAround):
            n = nAlongSegment * elementsCountAround + n1
            # Calculate norm
            sd1Normalised = vector.normalise(sd1[nAlongSegment])
            v = [xWarpedList[n][c] - sx[nAlongSegment][c] for c in range(3)]
            dp = vector.dotproduct(v, sd1Normalised)
            dpScaled = [dp * c for c in sd1Normalised]
            vProjected = [v[c] - dpScaled[c] for c in range(3)]
            vProjectedList.append(vProjected)
            if vector.magnitude(vProjected) > 0.0:
                vProjectedNormlised = vector.normalise(vProjected)
            else:
                vProjectedNormlised = [0.0, 0.0, 0.0]

            # Calculate curvature along at each node
            if nAlongSegment == 0:
                curvature = interp.getCubicHermiteCurvature(
                    sx[0], sd1[0], sx[1], sd1[1], vProjectedNormlised, 0.0)
            elif nAlongSegment == elementsCountAlongSegment:
                curvature = interp.getCubicHermiteCurvature(
                    sx[-2], sd1[-2], sx[-1], sd1[-1], vProjectedNormlised, 1.0)
            else:
                curvature = 0.5 * (interp.getCubicHermiteCurvature(
                    sx[nAlongSegment - 1], sd1[nAlongSegment - 1],
                    sx[nAlongSegment], sd1[nAlongSegment], vProjectedNormlised,
                    1.0) + interp.getCubicHermiteCurvature(
                        sx[nAlongSegment], sd1[nAlongSegment],
                        sx[nAlongSegment + 1], sd1[nAlongSegment + 1],
                        vProjectedNormlised, 0.0))
            # Scale
            factor = 1.0 - curvature * innerRadiusAlong[nAlongSegment]
            d2 = [factor * c for c in d2WarpedList[n]]
            d2WarpedListScaled.append(d2)

    # Smooth d2 for segment
    smoothd2Raw = []
    for n1 in range(elementsCountAround):
        nx = []
        nd2 = []
        for n2 in range(elementsCountAlongSegment + 1):
            n = n2 * elementsCountAround + n1
            nx.append(xWarpedList[n])
            nd2.append(d2WarpedListScaled[n])
        smoothd2 = interp.smoothCubicHermiteDerivativesLine(
            nx, nd2, fixStartDerivative=True, fixEndDerivative=True)
        smoothd2Raw.append(smoothd2)

    # Re-arrange smoothd2
    for n2 in range(elementsCountAlongSegment + 1):
        for n1 in range(elementsCountAround):
            d2WarpedListFinal.append(smoothd2Raw[n1][n2])

    # Calculate unit d3
    for n in range(len(xWarpedList)):
        d3Unit = vector.normalise(
            vector.crossproduct3(vector.normalise(d1WarpedList[n]),
                                 vector.normalise(d2WarpedListFinal[n])))
        d3WarpedUnitList.append(d3Unit)

    return xWarpedList, d1WarpedList, d2WarpedListFinal, d3WarpedUnitList
Esempio n. 23
0
def getCoordinatesFromInner(xInner, d1Inner, d2Inner, d3Inner,
                            wallThicknessList, relativeThicknessList,
                            elementsCountAround, elementsCountAlong,
                            elementsCountThroughWall, transitElementList):
    """
    Generates coordinates from inner to outer surface using coordinates
    and derivatives of inner surface.
    :param xInner: Coordinates on inner surface
    :param d1Inner: Derivatives on inner surface around tube
    :param d2Inner: Derivatives on inner surface along tube
    :param d3Inner: Derivatives on inner surface through wall
    :param wallThicknessList: Wall thickness for each element along tube
    :param relativeThicknessList: Relative wall thickness for each element through wall
    :param elementsCountAround: Number of elements around tube
    :param elementsCountAlong: Number of elements along tube
    :param elementsCountThroughWall: Number of elements through tube wall
    :param transitElementList: stores true if element around is a transition
    element that is between a big and a small element.
    return nodes and derivatives for mesh, and curvature along inner surface.
    """

    xOuter = []
    curvatureAroundInner = []
    curvatureAlong = []
    curvatureList = []
    xList = []
    d1List = []
    d2List = []
    d3List = []

    if relativeThicknessList:
        xi3 = 0.0
        xi3List = [0.0]
        for n3 in range(elementsCountThroughWall):
            xi3 += relativeThicknessList[n3]
            xi3List.append(xi3)
        relativeThicknessList.append(relativeThicknessList[-1])

    for n2 in range(elementsCountAlong + 1):
        wallThickness = wallThicknessList[n2]
        for n1 in range(elementsCountAround):
            n = n2 * elementsCountAround + n1
            norm = d3Inner[n]
            # Calculate outer coordinates
            x = [xInner[n][i] + norm[i] * wallThickness for i in range(3)]
            xOuter.append(x)
            # Calculate curvature along elements around
            prevIdx = n - 1 if (
                n1 != 0) else (n2 + 1) * elementsCountAround - 1
            nextIdx = n + 1 if (
                n1 < elementsCountAround - 1) else n2 * elementsCountAround
            kappam = interp.getCubicHermiteCurvatureSimple(
                xInner[prevIdx], d1Inner[prevIdx], xInner[n], d1Inner[n], 1.0)
            kappap = interp.getCubicHermiteCurvatureSimple(
                xInner[n], d1Inner[n], xInner[nextIdx], d1Inner[nextIdx], 0.0)
            if not transitElementList[n1] and not transitElementList[
                (n1 - 1) % elementsCountAround]:
                curvatureAround = 0.5 * (kappam + kappap)
            elif transitElementList[n1]:
                curvatureAround = kappam
            elif transitElementList[(n1 - 1) % elementsCountAround]:
                curvatureAround = kappap
            curvatureAroundInner.append(curvatureAround)

            # Calculate curvature along
            if n2 == 0:
                curvature = interp.getCubicHermiteCurvature(
                    xInner[n], d2Inner[n], xInner[n + elementsCountAround],
                    d2Inner[n + elementsCountAround],
                    vector.normalise(d3Inner[n]), 0.0)
            elif n2 == elementsCountAlong:
                curvature = interp.getCubicHermiteCurvature(
                    xInner[n - elementsCountAround],
                    d2Inner[n - elementsCountAround], xInner[n], d2Inner[n],
                    vector.normalise(d3Inner[n]), 1.0)
            else:
                curvature = 0.5 * (interp.getCubicHermiteCurvature(
                    xInner[n - elementsCountAround],
                    d2Inner[n - elementsCountAround], xInner[n], d2Inner[n],
                    vector.normalise(d3Inner[n]),
                    1.0) + interp.getCubicHermiteCurvature(
                        xInner[n], d2Inner[n], xInner[n + elementsCountAround],
                        d2Inner[n + elementsCountAround],
                        vector.normalise(d3Inner[n]), 0.0))
            curvatureAlong.append(curvature)

        for n3 in range(elementsCountThroughWall + 1):
            xi3 = xi3List[
                n3] if relativeThicknessList else 1.0 / elementsCountThroughWall * n3
            for n1 in range(elementsCountAround):
                n = n2 * elementsCountAround + n1
                norm = d3Inner[n]
                innerx = xInner[n]
                outerx = xOuter[n]
                dWall = [wallThickness * c for c in norm]
                # x
                x = interp.interpolateCubicHermite(innerx, dWall, outerx,
                                                   dWall, xi3)
                xList.append(x)

                # dx_ds1
                factor = 1.0 + wallThickness * xi3 * curvatureAroundInner[n]
                d1 = [factor * c for c in d1Inner[n]]
                d1List.append(d1)

                # dx_ds2
                curvature = curvatureAlong[n]
                distance = vector.magnitude(
                    [x[i] - xInner[n][i] for i in range(3)])
                factor = 1.0 - curvature * distance
                d2 = [factor * c for c in d2Inner[n]]
                d2List.append(d2)
                curvatureList.append(curvature)

                #dx_ds3
                d3 = [
                    c * wallThickness *
                    (relativeThicknessList[n3] if relativeThicknessList else
                     1.0 / elementsCountThroughWall) for c in norm
                ]
                d3List.append(d3)

    return xList, d1List, d2List, d3List, curvatureList
Esempio n. 24
0
def getPlaneProjectionOnCentralPath(x, elementsCountAround, elementsCountAlong,
                                    segmentLength, sx, sd1, sd2, sd12):
    """
    Projects reference point used for warping onto the central path and find coordinates
    and derivatives at projected location.
    :param x: coordinates of nodes.
    :param elementsCountAround: number of elements around.
    :param elementsCountAlong: number of elements along.
    :param segmentLength: Length of segment.
    :param sx: coordinates of equally spaced points on central path.
    :param sd1: tangent of equally spaced points on central path.
    :param sd2: derivative representing cross axis at equally spaced points on central path.
    :param sd12: rate of change of cross axis at equally spaced points on central path.
    :return: coordinates and derivatives on project points and z-coordinates of reference points.
    """

    # Use first node in each group of elements along as reference for warping later
    zRefList = []
    for n2 in range(elementsCountAlong + 1):
        zFirstNodeAlong = x[n2 * elementsCountAround][2]
        zRefList.append(zFirstNodeAlong)

    # Find sx, sd1, sd2 at projection of reference points on central path
    lengthElementAlong = segmentLength / elementsCountAlong

    # Append values from first node on central path
    sxRefList = []
    sd1RefList = []
    sd2RefList = []
    sxRefList.append(sx[0])
    sd1RefList.append(sd1[0])
    sd2RefList.append(sd2[0])

    # Interpolate the ones in between
    for n2 in range(1, elementsCountAlong):
        ei = int(zRefList[n2] // lengthElementAlong + 1)
        xi = (zRefList[n2] - lengthElementAlong *
              (ei - 1)) / lengthElementAlong
        sxRef = interp.interpolateCubicHermite(sx[ei - 1], sd1[ei - 1], sx[ei],
                                               sd1[ei], xi)
        sd1Ref = interp.interpolateCubicHermiteDerivative(
            sx[ei - 1], sd1[ei - 1], sx[ei], sd1[ei], xi)
        sd2Ref = interp.interpolateCubicHermite(sd2[ei - 1], sd12[ei - 1],
                                                sd2[ei], sd12[ei], xi)
        sxRefList.append(sxRef)
        sd1RefList.append(sd1Ref)
        sd2RefList.append(sd2Ref)

    # Append values from last node on central path
    sxRefList.append(sx[-1])
    sd1RefList.append(sd1[-1])
    sd2RefList.append(sd2[-1])

    # Project sd2 to plane orthogonal to sd1
    sd2ProjectedListRef = []

    for n in range(len(sd2RefList)):
        sd1Normalised = vector.normalise(sd1RefList[n])
        dp = vector.dotproduct(sd2RefList[n], sd1Normalised)
        dpScaled = [dp * c for c in sd1Normalised]
        sd2Projected = vector.normalise(
            [sd2RefList[n][c] - dpScaled[c] for c in range(3)])
        sd2ProjectedListRef.append(sd2Projected)

    return sxRefList, sd1RefList, sd2ProjectedListRef, zRefList
    def generateBaseMesh(cls, region, options):
        """
        Generate the base tricubic Hermite mesh. See also generateMesh().
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: annotationGroups
        """
        centralPath = options['Central path']
        segmentCount = options['Number of segments']
        elementsCountAround = options['Number of elements around']
        elementsCountAlongSegment = options['Number of elements along segment']
        elementsCountThroughWall = options['Number of elements through wall']
        duodenumLength = options['Duodenum length']
        jejunumLength = options['Jejunum length']
        duodenumInnerRadius = options['Duodenum inner radius']
        duodenumJejunumInnerRadius = options['Duodenum-jejunum inner radius']
        jejunumIleumInnerRadius = options['Jejunum-ileum inner radius']
        ileumInnerRadius = options['Ileum inner radius']
        wallThickness = options['Wall thickness']
        useCrossDerivatives = options['Use cross derivatives']
        useCubicHermiteThroughWall = not(options['Use linear through wall'])
        elementsCountAlong = int(elementsCountAlongSegment*segmentCount)
        startPhase = 0.0

        firstNodeIdentifier = 1
        firstElementIdentifier = 1

        # Central path
        tmpRegion = region.createRegion()
        centralPath.generate(tmpRegion)
        cx, cd1, cd2, cd12 = extractPathParametersFromRegion(tmpRegion)
        # for i in range(len(cx)):
        #     print(i, '[', cx[i], ',', cd1[i], ',', cd2[i],',', cd12[i], '],')
        del tmpRegion

        # find arclength of colon
        length = 0.0
        elementsCountIn = len(cx) - 1
        sd1 = interp.smoothCubicHermiteDerivativesLine(cx, cd1, fixAllDirections = True,
            magnitudeScalingMode = interp.DerivativeScalingMode.HARMONIC_MEAN)
        for e in range(elementsCountIn):
            arcLength = interp.getCubicHermiteArcLength(cx[e], sd1[e], cx[e + 1], sd1[e + 1])
            # print(e+1, arcLength)
            length += arcLength
        segmentLength = length / segmentCount
        elementAlongLength = length / elementsCountAlong
        # print('Length = ', length)

        # Sample central path
        sx, sd1, se, sxi, ssf = interp.sampleCubicHermiteCurves(cx, cd1, elementsCountAlongSegment*segmentCount)
        sd2, sd12 = interp.interpolateSampleCubicHermite(cd2, cd12, se, sxi, ssf)

        # Generate variation of radius & tc width along length
        lengthList = [0.0, duodenumLength, duodenumLength + jejunumLength, length]
        innerRadiusList = [duodenumInnerRadius, duodenumJejunumInnerRadius, jejunumIleumInnerRadius, ileumInnerRadius]
        innerRadiusSegmentList, dInnerRadiusSegmentList = interp.sampleParameterAlongLine(lengthList, innerRadiusList,
                                                                                          segmentCount)

        # Create annotation groups for small intestine sections
        elementsAlongDuodenum = round(duodenumLength / elementAlongLength)
        elementsAlongJejunum = round(jejunumLength / elementAlongLength)
        elementsAlongIleum = elementsCountAlong - elementsAlongDuodenum - elementsAlongJejunum
        elementsCountAlongGroups = [elementsAlongDuodenum, elementsAlongJejunum, elementsAlongIleum]

        smallintestineGroup = AnnotationGroup(region, get_smallintestine_term("small intestine"))
        duodenumGroup = AnnotationGroup(region, get_smallintestine_term("duodenum"))
        jejunumGroup = AnnotationGroup(region, get_smallintestine_term("jejunum"))
        ileumGroup = AnnotationGroup(region, get_smallintestine_term("ileum"))

        annotationGroupAlong = [[smallintestineGroup, duodenumGroup],
                                [smallintestineGroup, jejunumGroup],
                                [smallintestineGroup, ileumGroup]]

        annotationGroupsAlong = []
        for i in range(len(elementsCountAlongGroups)):
            elementsCount = elementsCountAlongGroups[i]
            for n in range(elementsCount):
                annotationGroupsAlong.append(annotationGroupAlong[i])

        annotationGroupsAround = []
        for i in range(elementsCountAround):
            annotationGroupsAround.append([ ])

        annotationGroupsThroughWall = []
        for i in range(elementsCountThroughWall):
            annotationGroupsThroughWall.append([ ])

        xExtrude = []
        d1Extrude = []
        d2Extrude = []
        d3UnitExtrude = []

        # Create object
        smallIntestineSegmentTubeMeshInnerPoints = CylindricalSegmentTubeMeshInnerPoints(
            elementsCountAround, elementsCountAlongSegment, segmentLength,
            wallThickness, innerRadiusSegmentList, dInnerRadiusSegmentList, startPhase)

        for nSegment in range(segmentCount):
            # Create inner points
            xInner, d1Inner, d2Inner, transitElementList, segmentAxis, radiusAlongSegmentList = \
               smallIntestineSegmentTubeMeshInnerPoints.getCylindricalSegmentTubeMeshInnerPoints(nSegment)

            # Project reference point for warping onto central path
            start = nSegment*elementsCountAlongSegment
            end = (nSegment + 1)*elementsCountAlongSegment + 1
            sxRefList, sd1RefList, sd2ProjectedListRef, zRefList = \
                tubemesh.getPlaneProjectionOnCentralPath(xInner, elementsCountAround, elementsCountAlongSegment,
                                                         segmentLength, sx[start:end], sd1[start:end], sd2[start:end],
                                                         sd12[start:end])

            # Warp segment points
            xWarpedList, d1WarpedList, d2WarpedList, d3WarpedUnitList = tubemesh.warpSegmentPoints(
                xInner, d1Inner, d2Inner, segmentAxis, sxRefList, sd1RefList, sd2ProjectedListRef,
                elementsCountAround, elementsCountAlongSegment, zRefList, radiusAlongSegmentList,
                closedProximalEnd=False)

            # Store points along length
            xExtrude = xExtrude + (xWarpedList if nSegment == 0 else xWarpedList[elementsCountAround:])
            d1Extrude = d1Extrude + (d1WarpedList if nSegment == 0 else d1WarpedList[elementsCountAround:])

            # Smooth d2 for nodes between segments and recalculate d3
            if nSegment == 0:
                d2Extrude = d2Extrude + (d2WarpedList[:-elementsCountAround])
                d3UnitExtrude = d3UnitExtrude + (d3WarpedUnitList[:-elementsCountAround])
            else:
                xSecondFace = xWarpedList[elementsCountAround:elementsCountAround*2]
                d2SecondFace = d2WarpedList[elementsCountAround:elementsCountAround*2]
                for n1 in range(elementsCountAround):
                    nx = [xLastTwoFaces[n1], xLastTwoFaces[n1 + elementsCountAround], xSecondFace[n1]]
                    nd2 = [d2LastTwoFaces[n1], d2LastTwoFaces[n1 + elementsCountAround], d2SecondFace[n1]]
                    d2 = interp.smoothCubicHermiteDerivativesLine(nx, nd2, fixStartDerivative = True,
                                                                  fixEndDerivative = True)[1]
                    d2Extrude.append(d2)
                    d3Unit = vector.normalise(vector.crossproduct3(vector.normalise(d1LastTwoFaces[n1 + elementsCountAround]),
                                                                   vector.normalise(d2)))
                    d3UnitExtrude.append(d3Unit)
                d2Extrude = d2Extrude + \
                            (d2WarpedList[elementsCountAround:-elementsCountAround] if nSegment < segmentCount - 1 else
                             d2WarpedList[elementsCountAround:])
                d3UnitExtrude = d3UnitExtrude + \
                                (d3WarpedUnitList[elementsCountAround:-elementsCountAround] if nSegment < segmentCount - 1 else
                                 d3WarpedUnitList[elementsCountAround:])
            xLastTwoFaces = xWarpedList[-elementsCountAround*2:]
            d1LastTwoFaces = d1WarpedList[-elementsCountAround*2:]
            d2LastTwoFaces = d2WarpedList[-elementsCountAround*2:]

        # Create coordinates and derivatives
        xList, d1List, d2List, d3List, curvatureList = tubemesh.getCoordinatesFromInner(xExtrude, d1Extrude,
            d2Extrude, d3UnitExtrude, [wallThickness]*(elementsCountAlong+1),
            elementsCountAround, elementsCountAlong, elementsCountThroughWall, transitElementList)

        flatWidthList, xiList = smallIntestineSegmentTubeMeshInnerPoints.getFlatWidthAndXiList()

        # Create flat and texture coordinates
        xFlat, d1Flat, d2Flat, xTexture, d1Texture, d2Texture = tubemesh.createFlatAndTextureCoordinates(
            xiList, flatWidthList, length, wallThickness, elementsCountAround,
            elementsCountAlong, elementsCountThroughWall, transitElementList)

        # Create nodes and elements
        nextNodeIdentifier, nextElementIdentifier, annotationGroups = tubemesh.createNodesAndElements(
            region, xList, d1List, d2List, d3List, xFlat, d1Flat, d2Flat, xTexture, d1Texture, d2Texture,
            elementsCountAround, elementsCountAlong, elementsCountThroughWall,
            annotationGroupsAround, annotationGroupsAlong, annotationGroupsThroughWall,
            firstNodeIdentifier, firstElementIdentifier, useCubicHermiteThroughWall, useCrossDerivatives,
            closedProximalEnd=False)

        return annotationGroups
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
Esempio n. 27
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
Esempio n. 28
0
def generateOstiumMesh(region,
                       options,
                       trackSurface,
                       centrePosition,
                       axis1,
                       startNodeIdentifier=1,
                       startElementIdentifier=1,
                       vesselMeshGroups=None,
                       ostiumMeshGroups=None):
    '''
    :param vesselMeshGroups: List (over number of vessels) of list of mesh groups to add vessel elements to.
    :param ostiumMeshGroups: List of mesh groups to add only row of elements at ostium end 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()

    nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
    nodeIdentifier = startNodeIdentifier

    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)

    mesh = fm.findMeshByDimension(3)
    elementIdentifier = startElementIdentifier

    # 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 = []
    if (vesselWallThickness > 0.0) and (ostiumWallThickness > 0.0):
        commonOstiumWallThickness = 2.0 / (1.0 / vesselWallThickness +
                                           1.0 / ostiumWallThickness)
    else:
        commonOstiumWallThickness = vesselWallThickness

    # 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]),
                                commonOstiumWallThickness)
            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 [1]:  # was range(2), now using curvature for inside:
            #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

    # calculate inner d2 derivatives around ostium from outer using track surface curvature
    factor = 1.0
    for n1 in range(elementsCountAroundOstium):
        trackDirection = vector.normalise(od2[1][n1])
        trackDistance = factor * vector.magnitude(od2[1][n1])
        tx = [None, ox[1][n1], None]
        td1 = [None, vector.setMagnitude(od2[1][n1], trackDistance), None]
        td2 = [None, vector.setMagnitude(od1[1][n1], -trackDistance), None]
        positionBackward = trackSurface.trackVector(oPositions[n1],
                                                    trackDirection,
                                                    -trackDistance)
        tx[0], d1, d2 = trackSurface.evaluateCoordinates(positionBackward,
                                                         derivatives=True)
        sd1, sd2, sd3 = calculate_surface_axes(d1, d2, trackDirection)
        td1[0] = vector.setMagnitude(sd1, trackDistance)
        td2[0] = vector.setMagnitude(sd2, trackDistance)
        positionForward = trackSurface.trackVector(oPositions[n1],
                                                   trackDirection,
                                                   trackDistance)
        tx[2], d1, d2 = trackSurface.evaluateCoordinates(positionForward,
                                                         derivatives=True)
        sd1, sd2, sd3 = calculate_surface_axes(d1, d2, trackDirection)
        td1[2] = vector.setMagnitude(sd1, trackDistance)
        td2[2] = vector.setMagnitude(sd2, trackDistance)
        newd2 = interp.projectHermiteCurvesThroughWall(tx, td1, td2, 1,
                                                       -ostiumWallThickness)[1]
        # assign components to set in all lists:
        for c in range(3):
            od2[0][n1][c] = newd2[c] / factor
        #for n in [ 0, 1, 2 ]:
        #    node = nodes.createNode(nodeIdentifier, nodetemplateLinearS3)
        #    cache.setNode(node)
        #    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, tx [n])
        #    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, td1[n])
        #    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, td2[n])
        #    nodeIdentifier += 1

    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
    ##############

    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
    #################

    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 vesselMeshGroups or ostiumMeshGroups:
            rowMeshGroups = []
            for i in range(elementsCountAlong):
                rowMeshGroups.append(
                    copy.copy(vesselMeshGroups[v]) if vesselMeshGroups else [])
        else:
            rowMeshGroups = None
        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]
            if ostiumMeshGroups:
                rowMeshGroups[0] += ostiumMeshGroups
        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]
            if ostiumMeshGroups:
                rowMeshGroups[-1] += ostiumMeshGroups

        #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,
            maxStartThickness=vesselWallThickness,
            maxEndThickness=vesselWallThickness,
            elementsCountRadial=elementsCountAlong,
            meshGroups=rowMeshGroups)

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