def get_bifurcation_triple_point(p1x, p1d, p2x, p2d, p3x, p3d): ''' Get coordinates and derivatives of triple point between p1, p2 and p3 with derivatives. :param p1x..p3d: Point coordinates and derivatives, numbered anticlockwise around triple point. All derivatives point away from triple point. Returned d1 points from triple point to p2, d2 points from triple point to p3. :return: x, d1, d2 ''' trx1 = interpolateCubicHermite(p1x, mult(p1d, -2.0), p2x, mult(p2d, 2.0), 0.5) trx2 = interpolateCubicHermite(p2x, mult(p2d, -2.0), p3x, mult(p3d, 2.0), 0.5) trx3 = interpolateCubicHermite(p3x, mult(p3d, -2.0), p1x, mult(p1d, 2.0), 0.5) trx = [ (trx1[c] + trx2[c] + trx3[c])/3.0 for c in range(3) ] td1 = interpolateLagrangeHermiteDerivative(trx, p1x, p1d, 0.0) td2 = interpolateLagrangeHermiteDerivative(trx, p2x, p2d, 0.0) td3 = interpolateLagrangeHermiteDerivative(trx, p3x, p3d, 0.0) n12 = cross(td1, td2) n23 = cross(td2, td3) n31 = cross(td3, td1) norm = normalize([ (n12[c] + n23[c] + n31[c]) for c in range(3) ]) sd1 = smoothCubicHermiteDerivativesLine([ trx, p1x ], [ normalize(cross(norm, cross(td1, norm))), p1d ], fixStartDirection=True, fixEndDerivative=True)[0] sd2 = smoothCubicHermiteDerivativesLine([ trx, p2x ], [ normalize(cross(norm, cross(td2, norm))), p2d ], fixStartDirection=True, fixEndDerivative=True)[0] sd3 = smoothCubicHermiteDerivativesLine([ trx, p3x ], [ normalize(cross(norm, cross(td3, norm))), p3d ], fixStartDirection=True, fixEndDerivative=True)[0] trd1 = mult(sub(sd2, add(sd3, sd1)), 0.5) trd2 = mult(sub(sd3, add(sd1, sd2)), 0.5) return trx, trd1, trd2
def smooth(self, updateDirections=False, maxIterations=10, arcLengthTolerance=1.0E-6): ''' :param maxIterations: Maximum iterations before stopping if not converging. :param arcLengthTolerance: Ratio of difference in arc length from last iteration divided by current arc length under which convergence is achieved. Required to be met by every element edge. ''' if not self._derivativeMap: return # no nodes being smoothed componentsCount = self._field.getNumberOfComponents() with ChangeManager(self._fieldmodule): fieldcache = self._fieldmodule.createFieldcache() for iter in range(maxIterations + 1): converged = True for edge in self._edgesMap.values(): lastArcLength = edge.getLastArcLength() arcLength = edge.evaluateArcLength(self._nodes, self._field, fieldcache) edge.updateLastArcLength() if (math.fabs(arcLength - lastArcLength) / arcLength) > arcLengthTolerance: converged = False if converged: print('Derivative smoothing: Converged after', iter, 'iterations.') break elif (iter == maxIterations): print('Derivative smoothing: Stopping after', maxIterations, 'iterations without converging.') break for derivativeKey, derivativeEdges in self._derivativeMap.items( ): edgeCount = len(derivativeEdges) if edgeCount > 1: nodeIdentifier, nodeValueLabel, nodeVersion = derivativeKey fieldcache.setNode( self._nodes.findNodeByIdentifier(nodeIdentifier)) if updateDirections: x = [0.0 for _ in range(componentsCount)] else: result, x = self._field.getNodeParameters( fieldcache, -1, nodeValueLabel, nodeVersion, componentsCount) mag = 0.0 for derivativeEdge in derivativeEdges: edge, expressionIndex, totalScaleFactor = derivativeEdge arcLength = edge.getArcLength() if updateDirections: delta = edge.getDelta() if totalScaleFactor < 0.0: delta = [-d for d in delta] for c in range(componentsCount): x[c] += delta[c] / arcLength if self._scalingMode == DerivativeScalingMode.ARITHMETIC_MEAN: mag += arcLength / math.fabs(totalScaleFactor) else: # self._scalingMode == DerivativeScalingMode.HARMONIC_MEAN mag += math.fabs(totalScaleFactor) / arcLength if self._scalingMode == DerivativeScalingMode.ARITHMETIC_MEAN: mag /= edgeCount else: # self._scalingMode == DerivativeScalingMode.HARMONIC_MEAN mag = edgeCount / mag if (mag <= 0.0): print('Node', nodeIdentifier, 'value', nodeValueLabel, 'version', nodeVersion, \ 'has negative mag', mag) x = setMagnitude(x, mag) result = self._field.setNodeParameters( fieldcache, -1, nodeValueLabel, nodeVersion, x) for derivativeKey, derivativeEdges in self._derivativeMap.items( ): edgeCount = len(derivativeEdges) if edgeCount == 1: # boundary smoothing over single edge nodeIdentifier, nodeValueLabel, nodeVersion = derivativeKey edge, expressionIndex, totalScaleFactor = derivativeEdges[ 0] # re-evaluate arc length so parameters are up-to-date for other end arcLength = edge.evaluateArcLength( self._nodes, self._field, fieldcache) fieldcache.setNode( self._nodes.findNodeByIdentifier(nodeIdentifier) ) # since changed by evaluateArcLength otherExpressionIndex = 3 if (expressionIndex == 1) else 1 otherd = edge.getParameter(otherExpressionIndex) if updateDirections: thisx = edge.getParameter(expressionIndex - 1) otherx = edge.getParameter(otherExpressionIndex - 1) bothEndsOnBoundary = False otherExpression = edge.getExpression( otherExpressionIndex) if len(otherExpression) == 1: otherNodeIdentifier, otherValueLabel, otherNodeVersion, otherTotalScaleFactor = otherExpression[ 0] otherDerivativeKey = (otherNodeIdentifier, otherValueLabel, otherNodeVersion) otherDerivativeEdges = self._derivativeMap.get( otherDerivativeKey) bothEndsOnBoundary = ( otherDerivativeEdges is not None) and (len(otherDerivativeEdges) == 1) if bothEndsOnBoundary: if expressionIndex == 1: x = [(otherx[c] - thisx[c]) for c in range(componentsCount)] else: x = [(thisx[c] - otherx[c]) for c in range(componentsCount)] else: if expressionIndex == 1: x = interpolateLagrangeHermiteDerivative( thisx, otherx, otherd, 0.0) else: x = interpolateHermiteLagrangeDerivative( otherx, otherd, thisx, 1.0) x = [d / totalScaleFactor for d in x] else: result, x = self._field.getNodeParameters( fieldcache, -1, nodeValueLabel, nodeVersion, componentsCount) othermag = magnitude(otherd) mag = (2.0 * arcLength - othermag) / math.fabs(totalScaleFactor) if (mag <= 0.0): print('Derivative smoothing: Node', nodeIdentifier, 'label', nodeValueLabel, 'version', nodeVersion, 'has negative magnitude', mag) x = setMagnitude(x, mag) result = self._field.setNodeParameters( fieldcache, -1, nodeValueLabel, nodeVersion, x) # record modified nodes while ChangeManager is in effect if self._editNodesetGroup: for derivativeKey in self._derivativeMap: self._editNodesetGroup.addNode( (self._nodes.findNodeByIdentifier(derivativeKey[0]))) del fieldcache
def generateBaseMesh(cls, region, options, baseCentre=[0.0, 0.0, 0.0], axisSide1=[0.0, -1.0, 0.0], axisUp=[0.0, 0.0, 1.0]): """ Generate the base bicubic-linear Hermite mesh. See also generateMesh(). Optional extra parameters allow centre and axes to be set. :param region: Zinc region to define model in. Must be empty. :param options: Dict containing options. See getDefaultOptions(). :param baseCentre: Centre of valve on ventriculo-arterial junction. :param axisSide: Unit vector in first side direction where angle around starts. :param axisUp: Unit vector in outflow direction of valve. :return: list of AnnotationGroup """ unitScale = options['Unit scale'] outerHeight = unitScale * options['Outer height'] innerDepth = unitScale * options['Inner depth'] cuspHeight = unitScale * options['Cusp height'] innerRadius = unitScale * 0.5 * options['Inner diameter'] sinusRadialDisplacement = unitScale * options[ 'Sinus radial displacement'] wallThickness = unitScale * options['Wall thickness'] cuspThickness = unitScale * options['Cusp thickness'] aorticNotPulmonary = options['Aortic not pulmonary'] useCrossDerivatives = False fm = region.getFieldmodule() fm.beginChange() coordinates = zinc_utils.getOrCreateCoordinateField(fm) cache = fm.createFieldcache() if aorticNotPulmonary: arterialRootGroup = AnnotationGroup(region, 'root of aorta', FMANumber=3740, lyphID='Lyph ID unknown') cuspGroups = [ AnnotationGroup(region, 'posterior cusp of aortic valve', FMANumber=7253, lyphID='Lyph ID unknown'), AnnotationGroup(region, 'right cusp of aortic valve', FMANumber=7252, lyphID='Lyph ID unknown'), AnnotationGroup(region, 'left cusp of aortic valve', FMANumber=7251, lyphID='Lyph ID unknown') ] else: arterialRootGroup = AnnotationGroup(region, 'root of pulmonary trunk', FMANumber=8612, lyphID='Lyph ID unknown') cuspGroups = [ AnnotationGroup(region, 'right cusp of pulmonary valve', FMANumber=7250, lyphID='Lyph ID unknown'), AnnotationGroup(region, 'anterior cusp of pulmonary valve', FMANumber=7249, lyphID='Lyph ID unknown'), AnnotationGroup(region, 'left cusp of pulmonary valve', FMANumber=7247, lyphID='Lyph ID unknown') ] allGroups = [arterialRootGroup ] # groups that all elements in scaffold will go in annotationGroups = allGroups + cuspGroups # annotation fiducial points fiducialGroup = zinc_utils.getOrCreateGroupField(fm, 'fiducial') fiducialCoordinates = zinc_utils.getOrCreateCoordinateField( fm, 'fiducial_coordinates') fiducialLabel = zinc_utils.getOrCreateLabelField(fm, 'fiducial_label') #fiducialElementXi = zinc_utils.getOrCreateElementXiField(fm, 'fiducial_element_xi') datapoints = fm.findNodesetByFieldDomainType( Field.DOMAIN_TYPE_DATAPOINTS) fiducialPoints = zinc_utils.getOrCreateNodesetGroup( fiducialGroup, datapoints) datapointTemplateExternal = datapoints.createNodetemplate() datapointTemplateExternal.defineField(fiducialCoordinates) datapointTemplateExternal.defineField(fiducialLabel) ################# # Create nodes ################# nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) nodetemplate = nodes.createNodetemplate() nodetemplate.defineField(coordinates) nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_VALUE, 1) nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS1, 1) nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS2, 1) nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS3, 1) # most nodes in this scaffold do not have a DS3 derivative nodetemplateLinearS3 = nodes.createNodetemplate() nodetemplateLinearS3.defineField(coordinates) nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_VALUE, 1) nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS1, 1) nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS2, 1) # several only have a DS1 derivative nodetemplateLinearS2S3 = nodes.createNodetemplate() nodetemplateLinearS2S3.defineField(coordinates) nodetemplateLinearS2S3.setValueNumberOfVersions( coordinates, -1, Node.VALUE_LABEL_VALUE, 1) nodetemplateLinearS2S3.setValueNumberOfVersions( coordinates, -1, Node.VALUE_LABEL_D_DS1, 1) nodeIdentifier = max(1, zinc_utils.getMaximumNodeIdentifier(nodes) + 1) elementsCountAround = 6 radiansPerElementAround = 2.0 * math.pi / elementsCountAround axisSide2 = vector.crossproduct3(axisUp, axisSide1) outerRadius = innerRadius + wallThickness cuspOuterLength2 = 0.5 * getApproximateEllipsePerimeter( innerRadius, cuspHeight) cuspOuterWallArcLength = cuspOuterLength2 * innerRadius / ( innerRadius + cuspHeight) noduleOuterAxialArcLength = cuspOuterLength2 - cuspOuterWallArcLength noduleOuterRadialArcLength = innerRadius cuspOuterWalld1 = interp.interpolateLagrangeHermiteDerivative( [innerRadius, outerHeight + innerDepth - cuspHeight], [0.0, 0.0], [-innerRadius, 0.0], 0.0) sin60 = math.sin(math.pi / 3.0) cuspThicknessLowerFactor = 4.5 # GRC fudge factor cuspInnerLength2 = 0.5 * getApproximateEllipsePerimeter( innerRadius - cuspThickness / sin60, cuspHeight - cuspThicknessLowerFactor * cuspThickness) noduleInnerAxialArcLength = cuspInnerLength2 * ( cuspHeight - cuspThicknessLowerFactor * cuspThickness) / ( innerRadius - cuspThickness / sin60 + cuspHeight - cuspThicknessLowerFactor * cuspThickness) noduleInnerRadialArcLength = innerRadius - cuspThickness / math.tan( math.pi / 3.0) nMidCusp = 0 if aorticNotPulmonary else 1 # lower points ix, id1 = createCirclePoints( [(baseCentre[c] - axisUp[c] * innerDepth) for c in range(3)], [axisSide1[c] * innerRadius for c in range(3)], [axisSide2[c] * innerRadius for c in range(3)], elementsCountAround) ox, od1 = getSemilunarValveSinusPoints(baseCentre, axisSide1, axisSide2, outerRadius, sinusRadialDisplacement, startMidCusp=aorticNotPulmonary) lowerx, lowerd1 = [ix, ox], [id1, od1] # upper points topCentre = [(baseCentre[c] + axisUp[c] * outerHeight) for c in range(3)] # twice as many on inner: ix, id1 = createCirclePoints( topCentre, [axisSide1[c] * innerRadius for c in range(3)], [axisSide2[c] * innerRadius for c in range(3)], elementsCountAround * 2) # tweak inner points so elements attached to cusps are narrower cuspRadiansFactor = 0.25 # GRC fudge factor midDerivativeFactor = 1.0 + 0.5 * (1.0 - cuspRadiansFactor ) # GRC test compromise cuspAttachmentRadians = cuspRadiansFactor * radiansPerElementAround cuspAttachmentRadialDisplacement = wallThickness * 0.333 # GRC fudge factor cuspAttachmentRadius = innerRadius - cuspAttachmentRadialDisplacement for cusp in range(3): n1 = cusp * 2 - 1 + nMidCusp n2 = n1 * 2 id1[n2 + 2] = [2.0 * d for d in id1[n2 + 2]] # side 1 radiansAround = n1 * radiansPerElementAround + cuspAttachmentRadians rcosRadiansAround = cuspAttachmentRadius * math.cos(radiansAround) rsinRadiansAround = cuspAttachmentRadius * math.sin(radiansAround) ix[n2 + 1] = [(topCentre[c] + rcosRadiansAround * axisSide1[c] + rsinRadiansAround * axisSide2[c]) for c in range(3)] id1[n2 + 1] = interp.interpolateLagrangeHermiteDerivative( ix[n2 + 1], ix[n2 + 2], id1[n2 + 2], 0.0) # side 2 n1 = ((cusp + 1) * 2 - 1 + nMidCusp) % elementsCountAround n2 = n1 * 2 radiansAround = n1 * radiansPerElementAround - cuspAttachmentRadians rcosRadiansAround = cuspAttachmentRadius * math.cos(radiansAround) rsinRadiansAround = cuspAttachmentRadius * math.sin(radiansAround) ix[n2 - 1] = [(topCentre[c] + rcosRadiansAround * axisSide1[c] + rsinRadiansAround * axisSide2[c]) for c in range(3)] id1[n2 - 1] = interp.interpolateHermiteLagrangeDerivative( ix[n2 - 2], id1[n2 - 2], ix[n2 - 1], 1.0) ox, od1 = createCirclePoints( topCentre, [axisSide1[c] * outerRadius for c in range(3)], [axisSide2[c] * outerRadius for c in range(3)], elementsCountAround) upperx, upperd1 = [ix, ox], [id1, od1] # get lower and upper derivative 2 zero = [0.0, 0.0, 0.0] upperd2factor = outerHeight upd2 = [d * upperd2factor for d in axisUp] lowerOuterd2 = interp.smoothCubicHermiteDerivativesLine( [lowerx[1][nMidCusp], upperx[1][nMidCusp]], [upd2, upd2], fixStartDirection=True, fixEndDerivative=True)[0] lowerd2factor = 2.0 * (outerHeight + innerDepth) - upperd2factor lowerInnerd2 = [d * lowerd2factor for d in axisUp] lowerd2 = [[lowerInnerd2] * elementsCountAround, [lowerOuterd2] * elementsCountAround ] # some lowerd2[0] to be fitted below upperd2 = [[upd2] * (elementsCountAround * 2), [upd2] * elementsCountAround] # get lower and upper derivative 1 or 2 pointing to/from cusps for n1 in range(elementsCountAround): radiansAround = n1 * radiansPerElementAround cosRadiansAround = math.cos(radiansAround) sinRadiansAround = math.sin(radiansAround) if (n1 % 2) == nMidCusp: lowerd2[0][n1] = [ -cuspOuterWallArcLength * (cosRadiansAround * axisSide1[c] + sinRadiansAround * axisSide2[c]) for c in range(3) ] else: upperd1[0][n1 * 2] = [ (cuspOuterWalld1[0] * (cosRadiansAround * axisSide1[c] + sinRadiansAround * axisSide2[c]) + cuspOuterWalld1[1] * axisUp[c]) for c in range(3) ] # inner wall and mid sinus points; only every second one is used sinusDepth = innerDepth - cuspThicknessLowerFactor * cuspThickness # GRC test sinusCentre = [(baseCentre[c] - sinusDepth * axisUp[c]) for c in range(3)] sinusx, sinusd1 = createCirclePoints( sinusCentre, [axisSide1[c] * innerRadius for c in range(3)], [axisSide2[c] * innerRadius for c in range(3)], elementsCountAround) # get sinusd2, parallel to lower inclined lines sd2 = interp.smoothCubicHermiteDerivativesLine( [[innerRadius, -sinusDepth], [innerRadius, outerHeight]], [[wallThickness + sinusRadialDisplacement, innerDepth], [0.0, upperd2factor]], fixStartDirection=True, fixEndDerivative=True)[0] sinusd2 = [None] * elementsCountAround for cusp in range(3): n1 = cusp * 2 + nMidCusp radiansAround = n1 * radiansPerElementAround cosRadiansAround = math.cos(radiansAround) sinRadiansAround = math.sin(radiansAround) sinusd2[n1] = [(sd2[0] * (cosRadiansAround * axisSide1[c] + sinRadiansAround * axisSide2[c]) + sd2[1] * axisUp[c]) for c in range(3)] # get points on arc between mid sinus and upper cusp points arcx = [] arcd1 = [] scaled1 = 2.5 # GRC fudge factor for cusp in range(3): n1 = cusp * 2 + nMidCusp n1m = n1 - 1 n1p = (n1 + 1) % elementsCountAround n2m = n1m * 2 + 1 n2p = n1p * 2 - 1 ax, ad1 = interp.sampleCubicHermiteCurves( [upperx[0][n2m], sinusx[n1]], [[-scaled1 * d for d in upperd2[0][n2m]], [scaled1 * d for d in sinusd1[n1]]], elementsCountOut=2, addLengthStart=0.5 * vector.magnitude(upperd2[0][n2m]), lengthFractionStart=0.5, addLengthEnd=0.5 * vector.magnitude(sinusd1[n1]), lengthFractionEnd=0.5, arcLengthDerivatives=False)[0:2] arcx.append(ax[1]) arcd1.append(ad1[1]) ax, ad1 = interp.sampleCubicHermiteCurves( [ sinusx[n1], upperx[0][n2p], ], [[scaled1 * d for d in sinusd1[n1]], [scaled1 * d for d in upperd2[0][n2p]]], elementsCountOut=2, addLengthStart=0.5 * vector.magnitude(sinusd1[n1]), lengthFractionStart=0.5, addLengthEnd=0.5 * vector.magnitude(upperd2[0][n2p]), lengthFractionEnd=0.5, arcLengthDerivatives=False)[0:2] arcx.append(ax[1]) arcd1.append(ad1[1]) if nMidCusp == 0: arcx.append(arcx.pop(0)) arcd1.append(arcd1.pop(0)) # cusp nodule points noduleCentre = [(baseCentre[c] + axisUp[c] * (cuspHeight - innerDepth)) for c in range(3)] nodulex = [[], []] noduled1 = [[], []] noduled2 = [[], []] noduled3 = [[], []] cuspRadialThickness = cuspThickness / sin60 for i in range(3): nodulex[0].append(noduleCentre) n1 = i * 2 + nMidCusp radiansAround = n1 * radiansPerElementAround cosRadiansAround = math.cos(radiansAround) sinRadiansAround = math.sin(radiansAround) nodulex[1].append([(noduleCentre[c] + cuspRadialThickness * (cosRadiansAround * axisSide1[c] + sinRadiansAround * axisSide2[c])) for c in range(3)]) n1 = i * 2 - 1 + nMidCusp radiansAround = n1 * radiansPerElementAround cosRadiansAround = math.cos(radiansAround) sinRadiansAround = math.sin(radiansAround) noduled1[0].append([ noduleOuterRadialArcLength * (cosRadiansAround * axisSide1[c] + sinRadiansAround * axisSide2[c]) for c in range(3) ]) noduled1[1].append( vector.setMagnitude(noduled1[0][i], noduleInnerRadialArcLength)) n1 = i * 2 + 1 + nMidCusp radiansAround = n1 * radiansPerElementAround cosRadiansAround = math.cos(radiansAround) sinRadiansAround = math.sin(radiansAround) noduled2[0].append([ noduleOuterRadialArcLength * (cosRadiansAround * axisSide1[c] + sinRadiansAround * axisSide2[c]) for c in range(3) ]) noduled2[1].append( vector.setMagnitude(noduled2[0][i], noduleInnerRadialArcLength)) noduled3[0].append( [noduleOuterAxialArcLength * axisUp[c] for c in range(3)]) noduled3[1].append( [noduleInnerAxialArcLength * axisUp[c] for c in range(3)]) # Create nodes lowerNodeId = [[], []] for n3 in range(2): for n1 in range(elementsCountAround): node = nodes.createNode(nodeIdentifier, nodetemplateLinearS3) cache.setNode(node) coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, lowerx[n3][n1]) coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, lowerd1[n3][n1]) coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, lowerd2[n3][n1]) lowerNodeId[n3].append(nodeIdentifier) nodeIdentifier += 1 sinusNodeId = [] for n1 in range(elementsCountAround): if (n1 % 2) != nMidCusp: sinusNodeId.append(None) continue node = nodes.createNode(nodeIdentifier, nodetemplateLinearS3) cache.setNode(node) coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, sinusx[n1]) coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, sinusd1[n1]) coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, sinusd2[n1]) sinusNodeId.append(nodeIdentifier) nodeIdentifier += 1 arcNodeId = [] for n1 in range(elementsCountAround): node = nodes.createNode(nodeIdentifier, nodetemplateLinearS2S3) cache.setNode(node) coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, arcx[n1]) coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, arcd1[n1]) arcNodeId.append(nodeIdentifier) nodeIdentifier += 1 noduleNodeId = [[], []] for n3 in range(2): for n1 in range(3): node = nodes.createNode(nodeIdentifier, nodetemplate) cache.setNode(node) coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, nodulex[n3][n1]) coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, noduled1[n3][n1]) coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, noduled2[n3][n1]) coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, noduled3[n3][n1]) noduleNodeId[n3].append(nodeIdentifier) nodeIdentifier += 1 upperNodeId = [[], []] for n3 in range(2): for n1 in range(len(upperx[n3])): node = nodes.createNode(nodeIdentifier, nodetemplateLinearS3) cache.setNode(node) coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, upperx[n3][n1]) coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, upperd1[n3][n1]) coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, upperd2[n3][n1]) upperNodeId[n3].append(nodeIdentifier) nodeIdentifier += 1 ################# # Create elements ################# mesh = fm.findMeshByDimension(3) allMeshGroups = [allGroup.getMeshGroup(mesh) for allGroup in allGroups] cuspMeshGroups = [ cuspGroup.getMeshGroup(mesh) for cuspGroup in cuspGroups ] linearHermiteLinearBasis = fm.createElementbasis( 3, Elementbasis.FUNCTION_TYPE_LINEAR_LAGRANGE) linearHermiteLinearBasis.setFunctionType( 2, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE) hermiteLinearLinearBasis = fm.createElementbasis( 3, Elementbasis.FUNCTION_TYPE_LINEAR_LAGRANGE) hermiteLinearLinearBasis.setFunctionType( 1, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE) bicubichermitelinear = eftfactory_bicubichermitelinear( mesh, useCrossDerivatives) eftDefault = bicubichermitelinear.createEftNoCrossDerivatives() elementIdentifier = max( 1, zinc_utils.getMaximumElementIdentifier(mesh) + 1) elementtemplate1 = mesh.createElementtemplate() elementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE) # wall elements for cusp in range(3): n1 = cusp * 2 - 1 + nMidCusp n2 = n1 * 2 for e in range(6): eft1 = None scalefactors = None if (e == 0) or (e == 5): # 6 node linear-hermite-linear collapsed wedge element expanding from zero width on outer wall of root, attaching to vertical part of cusp eft1 = mesh.createElementfieldtemplate( linearHermiteLinearBasis) # switch mappings to use DS2 instead of default DS1 remapEftNodeValueLabel(eft1, [1, 2, 3, 4, 5, 6, 7, 8], Node.VALUE_LABEL_D_DS1, [(Node.VALUE_LABEL_D_DS2, [])]) if e == 0: nids = [ lowerNodeId[0][n1], arcNodeId[n1], upperNodeId[0][n2], upperNodeId[0][n2 + 1], lowerNodeId[1][n1], upperNodeId[1][n1] ] setEftScaleFactorIds(eft1, [1], []) scalefactors = [-1.0] remapEftNodeValueLabel(eft1, [2], Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [1])]) else: nids = [ arcNodeId[n1 + 1], lowerNodeId[0][n1 - 4], upperNodeId[0][n2 + 3], upperNodeId[0][n2 - 8], lowerNodeId[1][n1 - 4], upperNodeId[1][n1 - 4] ] remapEftNodeValueLabel(eft1, [1], Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [])]) ln_map = [1, 2, 3, 4, 5, 5, 6, 6] remapEftLocalNodes(eft1, 6, ln_map) elif (e == 1) or (e == 4): # 6 node hermite-linear-linear collapsed wedge element on lower wall eft1 = mesh.createElementfieldtemplate( hermiteLinearLinearBasis) if e == 1: nids = [ lowerNodeId[0][n1], lowerNodeId[0][n1 + 1], arcNodeId[n1], sinusNodeId[n1 + 1], lowerNodeId[1][n1], lowerNodeId[1][n1 + 1] ] else: nids = [ lowerNodeId[0][n1 + 1], lowerNodeId[0][n1 - 4], sinusNodeId[n1 + 1], arcNodeId[n1 + 1], lowerNodeId[1][n1 + 1], lowerNodeId[1][n1 - 4] ] ln_map = [1, 2, 3, 4, 5, 6, 5, 6] remapEftLocalNodes(eft1, 6, ln_map) else: # 8 node elements with wedges on two sides if e == 2: eft1 = bicubichermitelinear.createEftNoCrossDerivatives( ) setEftScaleFactorIds(eft1, [1], []) scalefactors = [-1.0] nids = [ arcNodeId[n1], sinusNodeId[n1 + 1], upperNodeId[0][n2 + 1], upperNodeId[0][n2 + 2], lowerNodeId[1][n1], lowerNodeId[1][n1 + 1], upperNodeId[1][n1], upperNodeId[1][n1 + 1] ] remapEftNodeValueLabel(eft1, [1], Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [1])]) else: eft1 = eftDefault nids = [ sinusNodeId[n1 + 1], arcNodeId[n1 + 1], upperNodeId[0][n2 + 2], upperNodeId[0][n2 + 3], lowerNodeId[1][n1 + 1], lowerNodeId[1][n1 - 4], upperNodeId[1][n1 + 1], upperNodeId[1][n1 - 4] ] remapEftNodeValueLabel(eft1, [2], Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [])]) result = elementtemplate1.defineField(coordinates, -1, eft1) element = mesh.createElement(elementIdentifier, elementtemplate1) result2 = element.setNodesByIdentifier(eft1, nids) if scalefactors: result3 = element.setScaleFactors(eft1, scalefactors) else: result3 = 7 #print('create arterial root wall', cusp, e, 'element',elementIdentifier, result, result2, result3, nids) elementIdentifier += 1 for meshGroup in allMeshGroups: meshGroup.addElement(element) # cusps (leaflets) for cusp in range(3): n1 = cusp * 2 - 1 + nMidCusp n2 = n1 * 2 meshGroups = allMeshGroups + [cuspMeshGroups[cusp]] for e in range(2): eft1 = bicubichermitelinear.createEftNoCrossDerivatives() setEftScaleFactorIds(eft1, [1], []) scalefactors = [-1.0] if e == 0: nids = [ lowerNodeId[0][n1], lowerNodeId[0][n1 + 1], upperNodeId[0][n2], noduleNodeId[0][cusp], arcNodeId[n1], sinusNodeId[n1 + 1], upperNodeId[0][n2 + 1], noduleNodeId[1][cusp] ] remapEftNodeValueLabel(eft1, [4, 8], Node.VALUE_LABEL_D_DS1, [(Node.VALUE_LABEL_D_DS1, [1])]) remapEftNodeValueLabel(eft1, [4, 8], Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS3, [])]) remapEftNodeValueLabel(eft1, [5], Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [1])]) remapEftNodeValueLabel(eft1, [6], Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS2, [1])]) remapEftNodeValueLabel(eft1, [7], Node.VALUE_LABEL_D_DS1, [(Node.VALUE_LABEL_D_DS1, [1])]) else: nids = [ lowerNodeId[0][n1 + 1], lowerNodeId[0][n1 - 4], noduleNodeId[0][cusp], upperNodeId[0][n2 - 8], sinusNodeId[n1 + 1], arcNodeId[n1 + 1], noduleNodeId[1][cusp], upperNodeId[0][n2 + 3] ] remapEftNodeValueLabel(eft1, [3, 7], Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS3, [])]) remapEftNodeValueLabel(eft1, [3, 7], Node.VALUE_LABEL_D_DS1, [(Node.VALUE_LABEL_D_DS2, [])]) remapEftNodeValueLabel(eft1, [4, 8], Node.VALUE_LABEL_D_DS1, [(Node.VALUE_LABEL_D_DS1, [1])]) remapEftNodeValueLabel(eft1, [5], Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS2, [1])]) remapEftNodeValueLabel(eft1, [6], Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [])]) result = elementtemplate1.defineField(coordinates, -1, eft1) element = mesh.createElement(elementIdentifier, elementtemplate1) result2 = element.setNodesByIdentifier(eft1, nids) if scalefactors: result3 = element.setScaleFactors(eft1, scalefactors) else: result3 = 7 #print('create semilunar cusp', cusp, e, 'element',elementIdentifier, result, result2, result3, nids) elementIdentifier += 1 for meshGroup in meshGroups: meshGroup.addElement(element) # create annotation points datapoint = fiducialPoints.createNode(-1, datapointTemplateExternal) cache.setNode(datapoint) fiducialCoordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, noduleCentre) fiducialLabel.assignString( cache, 'aortic valve ctr' if aorticNotPulmonary else 'pulmonary valve ctr') fm.endChange() return annotationGroups
def createHermiteCurvePoints(self, aProportion1, aProportion2, bProportion1, bProportion2, elementsCount, derivativeStart=None, derivativeEnd=None, curveMode=HermiteCurveMode.SMOOTH): ''' Create hermite curve points between two points a and b on the surface, each defined by their proportions over the surface in directions 1 and 2. Also returns cross direction 2 in plane of surface with similar magnitude to curve derivative 1, and unit surface normals. :param derivativeStart, derivativeEnd: Optional derivative vectors in 3-D world coordinates to match at the start and end of the curves. If omitted, fits in with other derivative or is in a straight line from a to b. :param elementsCount: Number of elements out. :return: nx[], nd1[], nd2[], nd3[], nProportions[] ''' #print('createHermiteCurvePoints', aProportion1, aProportion2, bProportion1, bProportion2, elementsCount, derivativeStart, derivativeEnd) if derivativeStart: position = self.createPositionProportion(aProportion1, aProportion2) _, sd1, sd2 = self.evaluateCoordinates(position, derivatives=True) delta_xi1, delta_xi2 = calculate_surface_delta_xi( sd1, sd2, derivativeStart) dp1Start = delta_xi1 / self.elementsCount1 if self.loop1: dp1Start *= 2.0 dp2Start = delta_xi2 / self.elementsCount2 derivativeMagnitudeStart = math.sqrt(dp1Start * dp1Start + dp2Start * dp2Start) dp1Start *= elementsCount dp2Start *= elementsCount #print('start delta_xi1', delta_xi1, 'delta_xi2', delta_xi2) #print('dp1Start', dp1Start, 'dp2Start', dp2Start) if derivativeEnd: position = self.createPositionProportion(bProportion1, bProportion2) _, sd1, sd2 = self.evaluateCoordinates(position, derivatives=True) delta_xi1, delta_xi2 = calculate_surface_delta_xi( sd1, sd2, derivativeEnd) dp1End = delta_xi1 / self.elementsCount1 dp2End = delta_xi2 / self.elementsCount2 derivativeMagnitudeEnd = math.sqrt(dp1End * dp1End + dp2End * dp2End) dp1End *= elementsCount if self.loop1: dp1End *= 2.0 dp2End *= elementsCount #print('end delta_xi1', delta_xi1, 'delta_xi2', delta_xi2) #print('dp1End', dp1End, 'dp2End', dp2End) if not derivativeStart: if derivativeEnd: dp1Start, dp2Start = interp.interpolateLagrangeHermiteDerivative( [aProportion1, aProportion2], [bProportion1, bProportion2], [dp1End, dp2End], 0.0) else: dp1Start = bProportion1 - aProportion1 dp2Start = bProportion2 - aProportion2 derivativeMagnitudeStart = math.sqrt( dp1Start * dp1Start + dp2Start * dp2Start) / elementsCount if not derivativeEnd: if derivativeStart: dp1End, dp2End = interp.interpolateHermiteLagrangeDerivative( [aProportion1, aProportion2], [dp1Start, dp2Start], [bProportion1, bProportion2], 1.0) else: dp1End = bProportion1 - aProportion1 dp2End = bProportion2 - aProportion2 derivativeMagnitudeEnd = math.sqrt(dp1End * dp1End + dp2End * dp2End) / elementsCount maxProportion1 = 2.0 if self.loop1 else 1.0 #print('derivativeMagnitudeStart', derivativeMagnitudeStart, 'derivativeMagnitudeEnd', derivativeMagnitudeEnd) proportions, dproportions = interp.sampleCubicHermiteCurvesSmooth([ [ aProportion1, aProportion2 ], [ bProportion1, bProportion2 ] ], \ [ [ dp1Start, dp2Start ], [ dp1End, dp2End ] ], elementsCount, derivativeMagnitudeStart, derivativeMagnitudeEnd)[0:2] if curveMode != self.HermiteCurveMode.SMOOTH: if derivativeStart and (curveMode in [ self.HermiteCurveMode.TRANSITION_START, self.HermiteCurveMode.TRANSITION_START_AND_END ]): addLengthStart = 0.5 * derivativeMagnitudeStart lengthFractionStart = 0.5 else: addLengthStart = 0.0 lengthFractionStart = 1.0 if derivativeEnd and (curveMode in [ self.HermiteCurveMode.TRANSITION_END, self.HermiteCurveMode.TRANSITION_START_AND_END ]): addLengthEnd = 0.5 * derivativeMagnitudeEnd lengthFractionEnd = 0.5 else: addLengthEnd = 0.0 lengthFractionEnd = 1.0 proportions, dproportions = interp.sampleCubicHermiteCurves( proportions, dproportions, elementsCount, addLengthStart, addLengthEnd, lengthFractionStart, lengthFractionEnd)[0:2] #print(' proportions', proportions) #print('dproportions', dproportions) nx = [] nd1 = [] nd2 = [] nd3 = [] for n in range(0, elementsCount + 1): position = self.createPositionProportion(proportions[n][0], proportions[n][1]) x, sd1, sd2 = self.evaluateCoordinates(position, derivatives=True) f1 = dproportions[n][0] * self.elementsCount1 / maxProportion1 f2 = dproportions[n][1] * self.elementsCount2 d1 = [(f1 * sd1[c] + f2 * sd2[c]) for c in range(3)] d3 = vector.crossproduct3(sd1, sd2) # handle zero magnitude of d3 mag = math.sqrt(sum(d3[c] * d3[c] for c in range(3))) if mag > 0.0: d3 = [(d3[c] / mag) for c in range(3)] d2 = vector.crossproduct3(d3, d1) nx.append(x) nd2.append(d2) nd1.append(d1) nd3.append(d3) #print('createHermiteCurvePoints end \n nx', nx,'\nnd1',nd1,'\nnd2',nd2,'\nnd3',nd3) return nx, nd1, nd2, nd3, proportions
def getPoints(cls, options): """ Get point coordinates and derivatives for the arterial valve ring. Optional extra parameters allow origin and orientation to be set. :param options: Dict containing options. See getDefaultOptions(). :return: x, d1, d2, d3 all indexed by [n3=wall][n2=inlet->outlet][n1=around] where d1 is around, d2 is in direction inlet->outlet. d3 is radial and undefined at n2 == 1. """ unitScale = options['Unit scale'] innerRadius = unitScale * 0.5 * options['Inner diameter'] innerRadialDisplacement = unitScale * options[ 'Inner radial displacement'] innerSinusRadialDisplacement = unitScale * options[ 'Inner sinus radial displacement'] outerAngleRadians = math.radians(options['Outer angle degrees']) outerHeight = unitScale * options['Outer height'] outerRadialDisplacement = unitScale * options[ 'Outer radial displacement'] outerSinusRadialDisplacement = unitScale * options[ 'Outer sinus radial displacement'] outletLength = unitScale * options['Outlet length'] sinusAngleRadians = math.radians(options['Sinus angle degrees']) sinusDepth = unitScale * options['Sinus depth'] wallThickness = unitScale * options['Wall thickness'] rotationAzimuthRadians = math.radians( options['Rotation azimuth degrees']) rotationElevationRadians = math.radians( options['Rotation elevation degrees']) rotationRollRadians = math.radians(options['Rotation roll degrees']) centre = [ unitScale * options['Translation x'], unitScale * options['Translation y'], unitScale * options['Translation z'] ] outerRadius = innerRadius + wallThickness innerInletRadius = innerRadius + innerRadialDisplacement innerInletSinusRadius = innerRadius + innerSinusRadialDisplacement elementsCountAround = 6 # fixed radiansPerElementAround = 2.0 * math.pi / elementsCountAround pi_3 = radiansPerElementAround #centre = [ 0.0, 0.0, 0.0 ] #axis1 = [ 1.0, 0.0, 0.0 ] #axis2 = [ 0.0, 1.0, 0.0 ] #axis3 = vector.crossproduct3(axis1, axis2) axis1, axis2, axis3 = eulerToRotationMatrix3([ rotationAzimuthRadians, rotationElevationRadians, rotationRollRadians ]) x = [[None, None], [None, None]] d1 = [[None, None], [None, None]] d2 = [[None, None], [None, None]] d3 = [[None, None], [None, None]] # inlet # inner layer, with sinuses outletz = outerHeight inletz = outletz - outletLength sinusz = -sinusDepth outletCentre = [(centre[c] + outletz * axis3[c]) for c in range(3)] inletCentre = [(centre[c] + inletz * axis3[c]) for c in range(3)] sinusCentre = [(centre[c] + sinusz * axis3[c]) for c in range(3)] # calculate magnitude of d1, d2 at inner sinus leafd1mag = innerInletRadius * radiansPerElementAround # was 0.5* leafd2r, leafd2z = interpolateLagrangeHermiteDerivative( [innerRadialDisplacement, 0.0], [0.0, outletLength], [0.0, outletLength], 0.0) sinusd1mag = innerInletSinusRadius * radiansPerElementAround # initial value only sinusd1mag = vector.magnitude( smoothCubicHermiteDerivativesLine( [[innerInletRadius, 0.0, inletz], [ innerInletSinusRadius * math.cos(pi_3), innerInletSinusRadius * math.sin(pi_3), sinusz ]], [[0.0, leafd1mag, 0.0], [ -sinusd1mag * math.sin(pi_3), sinusd1mag * math.cos(pi_3), 0.0 ]], fixStartDerivative=True, fixEndDirection=True)[1]) sinusd2r, sinusd2z = smoothCubicHermiteDerivativesLine( [[innerInletSinusRadius, -sinusDepth], [innerInletRadius, outerHeight]], [[ outletLength * math.sin(sinusAngleRadians), outletLength * math.cos(sinusAngleRadians) ], [0.0, outletLength]], fixStartDirection=True, fixEndDerivative=True)[0] magd3 = wallThickness + outerRadialDisplacement - innerRadialDisplacement x[0][0] = [] d1[0][0] = [] d2[0][0] = [] d3[0][0] = [] for n1 in range(elementsCountAround): radiansAround = n1 * radiansPerElementAround cosRadiansAround = math.cos(radiansAround) sinRadiansAround = math.sin(radiansAround) if (n1 % 2) == 0: # leaflet junction cx = inletCentre r = innerInletRadius d1mag = leafd1mag d2mag1 = leafd2r * cosRadiansAround d2mag2 = leafd2r * sinRadiansAround d2mag3 = leafd2z else: # sinus / leaflet centre cx = sinusCentre r = innerInletSinusRadius d1mag = sinusd1mag d2mag1 = sinusd2r * cosRadiansAround d2mag2 = sinusd2r * sinRadiansAround d2mag3 = sinusd2z d3mag1 = magd3 * cosRadiansAround d3mag2 = magd3 * sinRadiansAround d3mag3 = 0.0 x[0][0].append([ (cx[c] + r * (cosRadiansAround * axis1[c] + sinRadiansAround * axis2[c])) for c in range(3) ]) d1[0][0].append([ d1mag * (-sinRadiansAround * axis1[c] + cosRadiansAround * axis2[c]) for c in range(3) ]) d2[0][0].append([ (d2mag1 * axis1[c] + d2mag2 * axis2[c] + d2mag3 * axis3[c]) for c in range(3) ]) d3[0][0].append([ (d3mag1 * axis1[c] + d3mag2 * axis2[c] + d3mag3 * axis3[c]) for c in range(3) ]) # outer layer extRadius = outerRadius + outerRadialDisplacement leafd2r, leafd2z = smoothCubicHermiteDerivativesLine( [[extRadius, 0.0], [outerRadius, outerHeight]], [[ -outerHeight * math.sin(outerAngleRadians), outerHeight * math.cos(outerAngleRadians) ], [0.0, outletLength]], fixStartDirection=True, fixEndDerivative=True)[0] # calculate magnitude of d1, d2 at outer sinus extSinusRadius = outerRadius + outerSinusRadialDisplacement leafd1mag = extRadius * radiansPerElementAround sinusd1mag = extSinusRadius * radiansPerElementAround # initial value only sinusd1mag = vector.magnitude( smoothCubicHermiteDerivativesLine( [[extRadius, 0.0, 0.0], [ extSinusRadius * math.cos(pi_3), extSinusRadius * math.sin(pi_3), 0.0 ]], [[0.0, leafd1mag, 0.0], [ -sinusd1mag * math.sin(pi_3), sinusd1mag * math.cos(pi_3), 0.0 ]], fixStartDerivative=True, fixEndDirection=True)[1]) sinusd2r, sinusd2z = smoothCubicHermiteDerivativesLine( [[extSinusRadius, 0.0], [outerRadius, outerHeight]], [[ -outerHeight * math.sin(outerAngleRadians), outerHeight * math.cos(outerAngleRadians) ], [0.0, outletLength]], fixStartDirection=True, fixEndDerivative=True)[0] centre = centre x[1][0] = [] d1[1][0] = [] d2[1][0] = [] d3[1][0] = [] for n1 in range(elementsCountAround): radiansAround = n1 * radiansPerElementAround cosRadiansAround = math.cos(radiansAround) sinRadiansAround = math.sin(radiansAround) if (n1 % 2) == 0: # leaflet junction cx = inletCentre r = extRadius d1mag = leafd1mag d2mag1 = leafd2r * cosRadiansAround d2mag2 = leafd2r * sinRadiansAround d2mag3 = leafd2z else: # sinus / leaflet centre cx = sinusCentre r = extSinusRadius d1mag = sinusd1mag d2mag1 = sinusd2r * cosRadiansAround d2mag2 = sinusd2r * sinRadiansAround d2mag3 = sinusd2z d3mag1 = magd3 * cosRadiansAround d3mag2 = magd3 * sinRadiansAround d3mag3 = 0.0 x[1][0].append([ (centre[c] + r * (cosRadiansAround * axis1[c] + sinRadiansAround * axis2[c])) for c in range(3) ]) d1[1][0].append([ d1mag * (-sinRadiansAround * axis1[c] + cosRadiansAround * axis2[c]) for c in range(3) ]) d2[1][0].append([ (d2mag1 * axis1[c] + d2mag2 * axis2[c] + d2mag3 * axis3[c]) for c in range(3) ]) d3[1][0].append([ (d3mag1 * axis1[c] + d3mag2 * axis2[c] + d3mag3 * axis3[c]) for c in range(3) ]) # outlet x[0][1], d1[0][1] = createCirclePoints( outletCentre, [axis1[c] * innerRadius for c in range(3)], [axis2[c] * innerRadius for c in range(3)], elementsCountAround) x[1][1], d1[1][1] = createCirclePoints( outletCentre, [axis1[c] * outerRadius for c in range(3)], [axis2[c] * outerRadius for c in range(3)], elementsCountAround) d2[1][1] = d2[0][1] = [[axis3[c] * outletLength for c in range(3)]] * elementsCountAround d3[1][1] = d3[0][1] = None return x, d1, d2, d3