diff --git a/scaffoldmaker/meshtypes/meshtype_3d_heart1.py b/scaffoldmaker/meshtypes/meshtype_3d_heart1.py index 5967ffce..150e7b0d 100644 --- a/scaffoldmaker/meshtypes/meshtype_3d_heart1.py +++ b/scaffoldmaker/meshtypes/meshtype_3d_heart1.py @@ -50,20 +50,22 @@ def getDefaultOptions(parameterSetName='Default'): def getOrderedOptionNames(): optionNames = MeshType_3d_heartventriclesbase1.getOrderedOptionNames() optionNamesAtria = MeshType_3d_heartatria1.getOrderedOptionNames() - # insert numbers of elements in atria in initial group - for optionName in [ - 'Number of elements around atrial free wall', + # insert new numbers of elements from atria; others came with ventriclesbase1 + optionNames.insert(7, 'Number of elements over atria') + # remove number of elements, unit scale and dependent options from atria options + for key in [ 'Number of elements around atrial septum', - 'Number of elements up atria', - 'Number of elements inlet']: - optionNames.insert(6, optionName) - optionNamesAtria.remove(optionName) - # remove dependent or repeated options in atria1 - optionNamesAtria.remove('Aorta outer plus diameter') - for optionName in optionNames: - if optionName in optionNamesAtria: - optionNamesAtria.remove(optionName) - # add remaining atria options + 'Number of elements around left atrium free wall', + 'Number of elements around right atrium free wall', + 'Number of elements over atria', + 'Unit scale', + 'Aorta outer plus diameter']: + optionNamesAtria.remove(key) + # remove duplicates from main options + for optionName in optionNamesAtria: + if optionName in optionNames: + optionNames.remove(optionName) + # add atria options all together optionNames += optionNamesAtria # want refinement options last for optionName in [ @@ -75,6 +77,18 @@ def getOrderedOptionNames(): optionNames.append(optionName) return optionNames + @classmethod + def getOptionValidScaffoldTypes(cls, optionName): + return MeshType_3d_heartatria1.getOptionValidScaffoldTypes(optionName) + + @classmethod + def getOptionScaffoldTypeParameterSetNames(cls, optionName, scaffoldType): + return MeshType_3d_heartatria1.getOptionScaffoldTypeParameterSetNames(optionName, scaffoldType) + + @classmethod + def getOptionScaffoldPackage(cls, optionName, scaffoldType, parameterSetName=None): + return MeshType_3d_heartatria1.getOptionScaffoldPackage(optionName, scaffoldType, parameterSetName) + @staticmethod def checkOptions(options): ''' @@ -97,9 +111,11 @@ def generateBaseMesh(cls, region, options): """ # set dependent outer diameter used in atria2 options['Aorta outer plus diameter'] = options['LV outlet inner diameter'] + 2.0*options['LV outlet wall thickness'] - elementsCountAroundAtrialFreeWall = options['Number of elements around atrial free wall'] elementsCountAroundAtrialSeptum = options['Number of elements around atrial septum'] - elementsCountAroundAtria = elementsCountAroundAtrialFreeWall + elementsCountAroundAtrialSeptum + elementsCountAroundLeftAtriumFreeWall = options['Number of elements around left atrium free wall'] + elementsCountAroundLeftAtrium = elementsCountAroundLeftAtriumFreeWall + elementsCountAroundAtrialSeptum + elementsCountAroundRightAtriumFreeWall = options['Number of elements around right atrium free wall'] + elementsCountAroundRightAtrium = elementsCountAroundRightAtriumFreeWall + elementsCountAroundAtrialSeptum useCrossDerivatives = False fm = region.getFieldmodule() @@ -107,7 +123,7 @@ def generateBaseMesh(cls, region, options): coordinates = zinc_utils.getOrCreateCoordinateField(fm) cache = fm.createFieldcache() - # generate heartventriclesbase2 model and put atria2 on it + # generate heartventriclesbase1 model and put atria1 on it annotationGroups = MeshType_3d_heartventriclesbase1.generateBaseMesh(region, options) annotationGroups += MeshType_3d_heartatria1.generateBaseMesh(region, options) lFibrousRingGroup = AnnotationGroup(region, 'left fibrous ring', FMANumber = 77124, lyphID = 'Lyph ID unknown') @@ -133,53 +149,57 @@ def generateBaseMesh(cls, region, options): # discover left and right fibrous ring nodes from ventricles and atria # because nodes are iterated in identifier order, the lowest and first are on the lv outlet cfb, right and left on lower outer layers - lavInnerNodeId = [ [], [] ] # [n2][n1] - lavOuterNodeId = [ [], [] ] # [n2][n1] + # left fibrous ring + lavNodeId = [ [ [], [] ], [ [], [] ] ] # [n3][n2][n1] iter = lFibrousRingGroup.getNodesetGroup(nodes).createNodeiterator() + # left fibrous ring, bottom row cfbNodeId = iter.next().getIdentifier() cfbLeftNodeId = iter.next().getIdentifier() - for n1 in range(elementsCountAroundAtria): - lavInnerNodeId[0].append(iter.next().getIdentifier()) - lavOuterNodeId[0].append(cfbNodeId) - lavOuterNodeId[0].append(cfbLeftNodeId) - for n1 in range(elementsCountAroundAtrialFreeWall - 1): - lavOuterNodeId[0].append(iter.next().getIdentifier()) + for n1 in range(elementsCountAroundLeftAtrium): + lavNodeId[0][0].append(iter.next().getIdentifier()) + lavNodeId[1][0].append(cfbNodeId) + lavNodeId[1][0].append(cfbLeftNodeId) + for n1 in range(elementsCountAroundLeftAtriumFreeWall - 1): + lavNodeId[1][0].append(iter.next().getIdentifier()) for n1 in range(elementsCountAroundAtrialSeptum - 1): - lavOuterNodeId[0].append(None) - for n1 in range(elementsCountAroundAtria): - lavInnerNodeId[1].append(iter.next().getIdentifier()) - for n1 in range(elementsCountAroundAtrialFreeWall + 1): - lavOuterNodeId[1].append(iter.next().getIdentifier()) + lavNodeId[1][0].append(None) # no outer node on interatrial septum + # left fibrous ring, top row + for n1 in range(elementsCountAroundLeftAtrium): + lavNodeId[0][1].append(iter.next().getIdentifier()) + for n1 in range(elementsCountAroundLeftAtriumFreeWall + 1): + lavNodeId[1][1].append(iter.next().getIdentifier()) for n1 in range(elementsCountAroundAtrialSeptum - 1): - lavOuterNodeId[1].append(None) - ravInnerNodeId = [ [], [] ] # [n2][n1] - ravOuterNodeId = [ [], [] ] # [n2][n1] + lavNodeId[1][1].append(None) # no outer node on interatrial septum + # right fibrous ring + ravNodeId = [ [ [], [] ], [ [], [] ] ] # [n3][n2][n1] iter = rFibrousRingGroup.getNodesetGroup(nodes).createNodeiterator() cfbNodeId = iter.next().getIdentifier() cfbRightNodeId = iter.next().getIdentifier() - for n1 in range(elementsCountAroundAtria): - ravInnerNodeId[0].append(iter.next().getIdentifier()) - for n1 in range(elementsCountAroundAtrialSeptum - 1): - ravOuterNodeId[0].append(None) - for n1 in range(elementsCountAroundAtrialFreeWall - 1): - ravOuterNodeId[0].append(iter.next().getIdentifier()) - ravOuterNodeId[0].append(cfbRightNodeId) - ravOuterNodeId[0].append(cfbNodeId) - for n1 in range(elementsCountAroundAtria): - ravInnerNodeId[1].append(iter.next().getIdentifier()) + # right fibrous ring, bottom row + for n1 in range(elementsCountAroundRightAtrium): + ravNodeId[0][0].append(iter.next().getIdentifier()) + for n1 in range(elementsCountAroundRightAtriumFreeWall - 1): + ravNodeId[1][0].append(iter.next().getIdentifier()) + ravNodeId[1][0].append(cfbRightNodeId) + ravNodeId[1][0].append(cfbNodeId) for n1 in range(elementsCountAroundAtrialSeptum - 1): - ravOuterNodeId[1].append(None) + ravNodeId[1][0].append(None) # no outer node on interatrial septum + # right fibrous ring, top row + for n1 in range(elementsCountAroundRightAtrium): + ravNodeId[0][1].append(iter.next().getIdentifier()) cfbUpperNodeId = iter.next().getIdentifier() # cfb from left will be first - for n1 in range(elementsCountAroundAtrialFreeWall): - ravOuterNodeId[1].append(iter.next().getIdentifier()) - ravOuterNodeId[1].append(cfbUpperNodeId) + for n1 in range(elementsCountAroundRightAtriumFreeWall): + ravNodeId[1][1].append(iter.next().getIdentifier()) + ravNodeId[1][1].append(cfbUpperNodeId) + for n1 in range(elementsCountAroundAtrialSeptum - 1): + ravNodeId[1][1].append(None) # no outer node on interatrial septum #for n2 in range(2): # print('n2', n2) - # print('lavInnerNodeId', lavInnerNodeId[n2]) - # print('lavOuterNodeId', lavOuterNodeId[n2]) - # print('ravInnerNodeId', ravInnerNodeId[n2]) - # print('ravOuterNodeId', ravOuterNodeId[n2]) + # print('lavNodeId[0]', lavNodeId[0][n2]) + # print('lavNodeId[1]', lavNodeId[1][n2]) + # print('ravNodeId[0]', ravNodeId[0][n2]) + # print('ravNodeId[1]', ravNodeId[1][n2]) ################# # Create elements @@ -202,19 +222,19 @@ def generateBaseMesh(cls, region, options): # left fibrous ring, starting at crux / collapsed posterior interatrial sulcus cruxElementId = None - for e in range(-1, elementsCountAroundAtrialFreeWall): + for e in range(-1, elementsCountAroundLeftAtriumFreeWall): eft1 = eftFibrousRing n1 = e nids = [ - lavInnerNodeId[0][n1], lavInnerNodeId[0][n1 + 1], lavInnerNodeId[1][n1], lavInnerNodeId[1][n1 + 1], - lavOuterNodeId[0][n1], lavOuterNodeId[0][n1 + 1], lavOuterNodeId[1][n1], lavOuterNodeId[1][n1 + 1]] + lavNodeId[0][0][n1], lavNodeId[0][0][n1 + 1], lavNodeId[0][1][n1], lavNodeId[0][1][n1 + 1], + lavNodeId[1][0][n1], lavNodeId[1][0][n1 + 1], lavNodeId[1][1][n1], lavNodeId[1][1][n1 + 1]] scalefactors = None meshGroups = [ lFibrousRingMeshGroup ] if e == -1: # interatrial groove straddles left and right atria, collapsed to 6 node wedge - nids[0] = ravInnerNodeId[0][-1] - nids[2] = ravInnerNodeId[1][-1] + nids[0] = ravNodeId[0][0][elementsCountAroundRightAtriumFreeWall] + nids[2] = ravNodeId[0][1][elementsCountAroundRightAtriumFreeWall] nids.pop(6) nids.pop(4) eft1 = bicubichermitelinear.createEftNoCrossDerivatives() @@ -248,7 +268,7 @@ def generateBaseMesh(cls, region, options): scalefactors = [ -1.0 ] remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS3, [1]) ]) - elif e == (elementsCountAroundAtrialFreeWall - 1): + elif e == (elementsCountAroundLeftAtriumFreeWall - 1): # general linear map d3 adjacent to collapsed sulcus eft1 = bicubichermitelinear.createEftNoCrossDerivatives() setEftScaleFactorIds(eft1, [1], []) @@ -269,20 +289,19 @@ def generateBaseMesh(cls, region, options): meshGroup.addElement(element) # right fibrous ring, starting at crux / collapsed posterior interatrial sulcus - ran1FreeWallStart = elementsCountAroundAtrialSeptum - 1 - for e in range(-1, elementsCountAroundAtrialFreeWall): + for e in range(-1, elementsCountAroundRightAtriumFreeWall): eft1 = eftFibrousRing - n1 = ran1FreeWallStart + e + n1 = e nids = [ - ravInnerNodeId[0][n1], ravInnerNodeId[0][n1 + 1], ravInnerNodeId[1][n1], ravInnerNodeId[1][n1 + 1], - ravOuterNodeId[0][n1], ravOuterNodeId[0][n1 + 1], ravOuterNodeId[1][n1], ravOuterNodeId[1][n1 + 1]] + ravNodeId[0][0][n1], ravNodeId[0][0][n1 + 1], ravNodeId[0][1][n1], ravNodeId[0][1][n1 + 1], + ravNodeId[1][0][n1], ravNodeId[1][0][n1 + 1], ravNodeId[1][1][n1], ravNodeId[1][1][n1 + 1]] scalefactors = None meshGroups = [ rFibrousRingMeshGroup ] if e == -1: # interatrial groove straddles left and right atria, collapsed to 6 node wedge - nids[0] = lavInnerNodeId[0][elementsCountAroundAtrialFreeWall] - nids[2] = lavInnerNodeId[1][elementsCountAroundAtrialFreeWall] + nids[0] = lavNodeId[0][0][elementsCountAroundLeftAtriumFreeWall] + nids[2] = lavNodeId[0][1][elementsCountAroundLeftAtriumFreeWall] nids.pop(6) nids.pop(4) eft1 = bicubichermitelinear.createEftNoCrossDerivatives() @@ -303,14 +322,14 @@ def generateBaseMesh(cls, region, options): setEftScaleFactorIds(eft1, [1], []) scalefactors = [ -1.0 ] remapEftNodeValueLabel(eft1, [ 5, 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) - elif e == (elementsCountAroundAtrialFreeWall - 2): + elif e == (elementsCountAroundRightAtriumFreeWall - 2): # reverse d1, d3 on right cfb: eft1 = bicubichermitelinear.createEftNoCrossDerivatives() setEftScaleFactorIds(eft1, [1], []) scalefactors = [ -1.0 ] remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [1]) ]) remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS3, [1]) ]) - elif e == (elementsCountAroundAtrialFreeWall - 1): + elif e == (elementsCountAroundRightAtriumFreeWall - 1): # general linear map d3 adjacent to collapsed cfb/anterior sulcus eft1 = bicubichermitelinear.createEftNoCrossDerivatives() setEftScaleFactorIds(eft1, [1], []) @@ -338,12 +357,12 @@ def generateBaseMesh(cls, region, options): meshGroups = [ lFibrousRingMeshGroup, rFibrousRingMeshGroup ] for e in range(elementsCountAroundAtrialSeptum): eft1 = eftFibrousRing - nlm = elementsCountAroundAtrialFreeWall + e - nlp = (nlm + 1)%elementsCountAroundAtria - nrm = elementsCountAroundAtrialSeptum - 1 - e + nlm = e - elementsCountAroundAtrialSeptum + nlp = nlm + 1 + nrm = -e nrp = nrm - 1 - nids = [ lavInnerNodeId[0][nlm], lavInnerNodeId[0][nlp], lavInnerNodeId[1][nlm], lavInnerNodeId[1][nlp], - ravInnerNodeId[0][nrm], ravInnerNodeId[0][nrp], ravInnerNodeId[1][nrm], ravInnerNodeId[1][nrp] ] + nids = [ lavNodeId[0][0][nlm], lavNodeId[0][0][nlp], lavNodeId[0][1][nlm], lavNodeId[0][1][nlp], + ravNodeId[0][0][nrm], ravNodeId[0][0][nrp], ravNodeId[0][1][nrm], ravNodeId[0][1][nrp] ] eft1 = bicubichermitelinear.createEftNoCrossDerivatives() setEftScaleFactorIds(eft1, [1], []) @@ -398,23 +417,23 @@ def refineMesh(cls, meshrefinement, options): :param options: Dict containing options. See getDefaultOptions(). """ assert isinstance(meshrefinement, MeshRefinement) - elementsCountAroundAtrialFreeWall = options['Number of elements around atrial free wall'] elementsCountAroundAtrialSeptum = options['Number of elements around atrial septum'] - elementsCountAroundAtria = elementsCountAroundAtrialFreeWall + elementsCountAroundAtrialSeptum + elementsCountAroundLeftAtriumFreeWall = options['Number of elements around left atrium free wall'] + elementsCountAroundRightAtriumFreeWall = options['Number of elements around right atrium free wall'] refineElementsCountSurface = options['Refine number of elements surface'] refineElementsCountThroughWall = options['Refine number of elements through wall'] MeshType_3d_heartventriclesbase1.refineMesh(meshrefinement, options) MeshType_3d_heartatria1.refineMesh(meshrefinement, options) element = meshrefinement._sourceElementiterator.next() startFibrousRingElementIdentifier = element.getIdentifier() - lastFibrousRingElementIdentifier = startFibrousRingElementIdentifier + 2*elementsCountAroundAtrialFreeWall + elementsCountAroundAtrialSeptum + 1 + lastFibrousRingElementIdentifier = startFibrousRingElementIdentifier + elementsCountAroundLeftAtriumFreeWall + elementsCountAroundRightAtriumFreeWall + elementsCountAroundAtrialSeptum + 1 i = -1 while element.isValid(): numberInXi1 = refineElementsCountSurface numberInXi2 = 1 # since fibrous ring is thin numberInXi3 = refineElementsCountThroughWall elementIdentifier = element.getIdentifier() - if i in [ -1, elementsCountAroundAtrialFreeWall ]: + if i in [ -1, elementsCountAroundLeftAtriumFreeWall ]: numberInXi1 = refineElementsCountThroughWall meshrefinement.refineElementCubeStandard3d(element, numberInXi1, numberInXi2, numberInXi3) if elementIdentifier == lastFibrousRingElementIdentifier: diff --git a/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py b/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py index 654c8c15..27b67c84 100644 --- a/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py +++ b/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py @@ -1,28 +1,159 @@ """ Generates a 3-D heart atria model, suitable for attachment to the -3-D Heart Ventricles with Base 2. +3-D Heart Ventricles with Base 1. """ from __future__ import division +import copy import math from scaffoldmaker.annotation.annotationgroup import AnnotationGroup, findAnnotationGroupByName from scaffoldmaker.meshtypes.scaffold_base import Scaffold_base +from scaffoldmaker.meshtypes.meshtype_3d_ostium1 import MeshType_3d_ostium1, generateOstiumMesh +from scaffoldmaker.scaffoldpackage import ScaffoldPackage +from scaffoldmaker.utils.annulusmesh import createAnnulusMesh3d from scaffoldmaker.utils.eft_utils import remapEftLocalNodes, remapEftNodeValueLabel, scaleEftNodeValueLabels, setEftScaleFactorIds -from scaffoldmaker.utils.geometry import getApproximateEllipsePerimeter, getEllipseArcLength, getEllipseRadiansToX, updateEllipseAngleByArcLength, createCirclePoints +from scaffoldmaker.utils.geometry import getApproximateEllipsePerimeter, getCircleProjectionAxes, getEllipseAngleFromVector, getEllipseArcLength, getEllipseRadiansToX, updateEllipseAngleByArcLength, createCirclePoints from scaffoldmaker.utils import interpolation as interp from scaffoldmaker.utils.meshrefinement import MeshRefinement from scaffoldmaker.utils import zinc_utils from scaffoldmaker.utils import vector from scaffoldmaker.utils.eftfactory_tricubichermite import eftfactory_tricubichermite +from scaffoldmaker.utils.tracksurface import TrackSurface, TrackSurfacePosition, calculate_surface_axes from opencmiss.zinc.element import Element, Elementbasis from opencmiss.zinc.field import Field from opencmiss.zinc.node import Node class MeshType_3d_heartatria1(Scaffold_base): ''' - 3-D heart atria model, suitable for attachment to the 3-D Heart Ventricles with Base 2. + 3-D heart atria model, suitable for attachment to the 3-D Heart Ventricles with Base 1. ''' + lpvOstiumDefaultScaffoldPackages = { + 'LPV Human 1' : ScaffoldPackage(MeshType_3d_ostium1, { + 'scaffoldSettings' : { + 'Number of vessels' : 2, + 'Number of elements across common' : 2, + 'Number of elements around ostium' : 12, + 'Number of elements along' : 1, + 'Number of elements through wall' : 1, + 'Unit scale' : 1.0, + 'Outlet' : False, + 'Ostium diameter' : 0.19, + 'Ostium length' : 0.04, + 'Ostium wall thickness' : 0.02, + 'Ostium inter-vessel distance' : 0.16, + 'Ostium inter-vessel height' : 0.0, + 'Use linear through ostium wall' : False, + 'Vessel end length factor' : 1.0, + 'Vessel inner diameter' : 0.11, + 'Vessel wall thickness' : 0.009, + 'Vessel angle 1 degrees' : 0.0, + 'Vessel angle 1 spread degrees' : 0.0, + 'Vessel angle 2 degrees' : 10.0, + 'Use linear through vessel wall' : True, + } + } ), + 'LPV Pig 1' : ScaffoldPackage(MeshType_3d_ostium1, { + 'scaffoldSettings' : { + 'Number of vessels' : 1, + 'Number of elements across common' : 2, + 'Number of elements around ostium' : 12, + 'Number of elements along' : 1, + 'Number of elements through wall' : 1, + 'Unit scale' : 1.0, + 'Outlet' : False, + 'Ostium diameter' : 0.23, + 'Ostium length' : 0.04, + 'Ostium wall thickness' : 0.02, + 'Ostium inter-vessel distance' : 0.16, + 'Ostium inter-vessel height' : 0.0, + 'Use linear through ostium wall' : False, + 'Vessel end length factor' : 1.0, + 'Vessel inner diameter' : 0.16, + 'Vessel wall thickness' : 0.011, + 'Vessel angle 1 degrees' : 0.0, + 'Vessel angle 1 spread degrees' : 0.0, + 'Vessel angle 2 degrees' : 0.0, + 'Use linear through vessel wall' : True, + } + } ), + 'LPV Rat 1' : ScaffoldPackage(MeshType_3d_ostium1, { + 'scaffoldSettings' : { + 'Number of vessels' : 3, + 'Number of elements across common' : 2, + 'Number of elements around ostium' : 12, + 'Number of elements along' : 1, + 'Number of elements through wall' : 1, + 'Unit scale' : 1.0, + 'Outlet' : False, + 'Ostium diameter' : 0.17, + 'Ostium length' : 0.04, + 'Ostium wall thickness' : 0.02, + 'Ostium inter-vessel distance' : 0.15, + 'Ostium inter-vessel height' : 0.02, + 'Use linear through ostium wall' : False, + 'Vessel end length factor' : 1.0, + 'Vessel inner diameter' : 0.13, + 'Vessel wall thickness' : 0.008, + 'Vessel angle 1 degrees' : 0.0, + 'Vessel angle 1 spread degrees' : 20.0, + 'Vessel angle 2 degrees' : 0.0, + 'Use linear through vessel wall' : True, + } + } ) + } + + rpvOstiumDefaultScaffoldPackages = { + 'RPV Human 1' : ScaffoldPackage(MeshType_3d_ostium1, { + 'scaffoldSettings' : { + 'Number of vessels' : 2, + 'Number of elements across common' : 2, + 'Number of elements around ostium' : 12, + 'Number of elements along' : 1, + 'Number of elements through wall' : 1, + 'Unit scale' : 1.0, + 'Outlet' : False, + 'Ostium diameter' : 0.2, + 'Ostium length' : 0.04, + 'Ostium wall thickness' : 0.02, + 'Ostium inter-vessel distance' : 0.2, + 'Ostium inter-vessel height' : 0.0, + 'Use linear through ostium wall' : False, + 'Vessel end length factor' : 1.0, + 'Vessel inner diameter' : 0.12, + 'Vessel wall thickness' : 0.009, + 'Vessel angle 1 degrees' : 0.0, + 'Vessel angle 1 spread degrees' : 0.0, + 'Vessel angle 2 degrees' : -10.0, + 'Use linear through vessel wall' : True, + } + } ), + 'RPV Pig 1' : ScaffoldPackage(MeshType_3d_ostium1, { + 'scaffoldSettings' : { + 'Number of vessels' : 1, + 'Number of elements across common' : 2, + 'Number of elements around ostium' : 12, + 'Number of elements along' : 1, + 'Number of elements through wall' : 1, + 'Unit scale' : 1.0, + 'Outlet' : False, + 'Ostium diameter' : 0.26, + 'Ostium length' : 0.04, + 'Ostium wall thickness' : 0.02, + 'Ostium inter-vessel distance' : 0.16, + 'Ostium inter-vessel height' : 0.0, + 'Use linear through ostium wall' : False, + 'Vessel end length factor' : 1.0, + 'Vessel inner diameter' : 0.19, + 'Vessel wall thickness' : 0.013, + 'Vessel angle 1 degrees' : 0.0, + 'Vessel angle 1 spread degrees' : 0.0, + 'Vessel angle 2 degrees' : 0.0, + 'Use linear through vessel wall' : True, + } + } ) + } + @staticmethod def getName(): return '3D Heart Atria 1' @@ -40,205 +171,415 @@ def getParameterSetNames(): 'Unit Pig 1', 'Unit Rat 1'] - @staticmethod - def getDefaultOptions(parameterSetName='Default'): - options = { - 'Number of elements around atrial free wall' : 8, - 'Number of elements around atrial septum' : 3, - 'Number of elements up atria' : 4, - 'Number of elements inlet' : 2, - 'Unit scale' : 1.0, - 'Atria base inner major axis length' : 0.55, - 'Atria base inner minor axis length' : 0.45, - 'Atria major axis rotation degrees' : 40.0, - 'Atria outer height' : 0.45, - 'Atrial septum thickness' : 0.07, - 'Atrial free wall thickness' : 0.02, - 'Atrial base wall thickness' : 0.07, - 'Atrial base slope degrees' : 30.0, - 'Aorta outer plus diameter' : 0.35, - 'Atrial base front incline degrees' : 15.0, - 'Atrial base back incline degrees' : 20.0, - 'Atrial base side incline degrees' : 20.0, - 'Atrial element size ratio anterior/posterior' : 1.5, - 'Number of left pulmonary veins' : 2, - 'Left pulmonary vein position up' : 0.6, - 'Left pulmonary vein angle up degrees' : 10.0, - 'Left pulmonary vein length factor' : 1.0, # multiple of inner diameter that inlet center is away from atria wall - 'Left pulmonary vein inner diameter' : 0.11, - 'Left pulmonary vein wall thickness' : 0.009, - 'Number of right pulmonary veins' : 2, - 'Right pulmonary vein position up' : 0.85, - 'Right pulmonary vein angle up degrees' : 0.0, - 'Right pulmonary vein length factor' : 1.0, # multiple of inner diameter that inlet center is away from atria wall - 'Right pulmonary vein inner diameter' : 0.12, - 'Right pulmonary vein wall thickness' : 0.009, - 'Inferior vena cava position up' : 0.3, - 'Inferior vena cava angle left degrees' : 0.0, - 'Inferior vena cava angle up degrees' : 10.0, - 'Inferior vena cava length factor' : 0.5, # multiple of inner diameter that inlet center is away from atria wall - 'Inferior vena cava inner diameter' : 0.22, - 'Inferior vena cava wall thickness' : 0.015, - 'Superior vena cava position up' : 0.7, - 'Superior vena cava angle up degrees' : 20.0, - 'Superior vena cava length factor' : 0.5, # multiple of inner diameter that inlet center is away from atria wall - 'Superior vena cava inner diameter' : 0.2, - 'Superior vena cava wall thickness' : 0.015, - 'Refine' : False, - 'Refine number of elements surface' : 4, - 'Refine number of elements through wall' : 1, - 'Use cross derivatives' : False, - } - if 'Human' in parameterSetName: - if 'Unit' not in parameterSetName: + @classmethod + def getDefaultOptions(cls, parameterSetName='Default'): + isHuman = 'Human' in parameterSetName + isMouse = 'Mouse' in parameterSetName + isPig = 'Pig' in parameterSetName + isRat = 'Rat' in parameterSetName + notUnitScale = 'Unit' not in parameterSetName + if isPig: + lpvOstium = cls.lpvOstiumDefaultScaffoldPackages['LPV Pig 1'] + rpvOstium = cls.rpvOstiumDefaultScaffoldPackages['RPV Pig 1'] + elif isMouse or isRat: + lpvOstium = cls.lpvOstiumDefaultScaffoldPackages['LPV Rat 1'] + rpvOstium = cls.rpvOstiumDefaultScaffoldPackages['RPV Human 1'] + else: + lpvOstium = cls.lpvOstiumDefaultScaffoldPackages['LPV Human 1'] + rpvOstium = cls.rpvOstiumDefaultScaffoldPackages['RPV Human 1'] + # to avoid circular include, repeat inter-dependent ventriclesbase options here: + ventriclesbaseOptions = {} + if isMouse or isRat: + ventriclesbaseOptions['LV outlet inner diameter'] = 0.23 if isRat else 0.21 + ventriclesbaseOptions['LV outlet wall thickness'] = 0.018 if isRat else 0.016 + else: + ventriclesbaseOptions['LV outlet inner diameter'] = 0.28 + ventriclesbaseOptions['LV outlet wall thickness'] = 0.022 + options = {} + options['Number of elements around atrial septum'] = 3 + options['Number of elements around left atrium free wall'] = 8 + options['Number of elements around right atrium free wall'] = 6 + options['Number of elements over atria'] = 6 + options['Unit scale'] = 1.0 + options['Atria base inner major axis length'] = 0.47 + options['Atria base inner minor axis length'] = 0.41 + options['Atria major axis rotation degrees'] = 40.0 + options['Atria outer height'] = 0.45 + options['Atrial septum height'] = 0.25 + options['Atrial septum length'] = 0.25 + options['Atrial septum thickness'] = 0.07 + options['Atrial vestibule outer height'] = 0.08 + options['Fossa ovalis height'] = 0.1 + options['Fossa ovalis length'] = 0.15 + options['Fossa ovalis thickness'] = 0.035 + options['Fossa ovalis midpoint height'] = 0.16 + options['Left atrium venous free wall thickness'] = 0.02 + options['Right atrium venous free wall thickness'] = 0.015 + options['Crista terminalis thickness'] = 0.03 + options['Atrial base wall thickness'] = 0.06 + options['Atrial base slope degrees'] = 30.0 + options['Aorta outer plus diameter'] = ventriclesbaseOptions['LV outlet inner diameter'] + 2.0*ventriclesbaseOptions['LV outlet wall thickness'] + options['Atrial base front incline degrees'] = 15.0 + options['Atrial base back incline degrees'] = 20.0 + options['Atrial base side incline degrees'] = 20.0 + options['Atria venous anterior over'] = 0.7 + options['Atria venous midpoint over'] = 0.41 + options['Left atrium venous midpoint left'] = 0.55 + options['Right atrium venous right'] = 0.4 + options['Left atrial appendage angle axial degrees'] = 0.0 + options['Left atrial appendage angle left degrees'] = 20.0 + options['Left atrial appendage angle up degrees'] = -60.0 + options['Left atrial appendage arc length'] = 0.3 + options['Left atrial appendage arc radius'] = 0.15 + options['Left atrial appendage base length'] = 0.3 + options['Left atrial appendage left'] = 0.85 + options['Left atrial appendage midpoint left'] = 0.45 + options['Left atrial appendage midpoint over'] = 0.95 + options['Left atrial appendage wall thickness'] = 0.025 + options['Left atrial appendage wedge angle degrees'] = 60.0 + options['Right atrial appendage angle axial degrees'] = 30.0 + options['Right atrial appendage angle left degrees'] = 40.0 + options['Right atrial appendage angle up degrees'] = -20.0 + options['Right atrial appendage arc length'] = 0.5 + options['Right atrial appendage arc radius'] = 0.17 + options['Right atrial appendage base length'] = 0.3 + options['Right atrial appendage midpoint right'] = 0.47 + options['Right atrial appendage midpoint over'] = 0.92 + options['Right atrial appendage pouch right'] = 0.9 + options['Right atrial appendage wall thickness'] = 0.025 + options['Right atrial appendage wedge angle degrees'] = 90.0 + options['Common left-right pulmonary vein ostium'] = False + options['Left pulmonary vein ostium'] = copy.deepcopy(lpvOstium) + options['Left pulmonary vein ostium angle degrees'] = 65.0 + options['Left pulmonary vein ostium position left'] = 0.64 + options['Left pulmonary vein ostium position over'] = 0.47 + options['Right pulmonary vein ostium'] = copy.deepcopy(rpvOstium) + options['Right pulmonary vein ostium angle degrees'] = 80.0 + options['Right pulmonary vein ostium position left'] = 0.16 + options['Right pulmonary vein ostium position over'] = 0.4 + options['Inferior vena cava inlet position over'] = 0.18 + options['Inferior vena cava inlet position right'] = 0.22 + options['Inferior vena cava inlet angle left degrees'] = -15.0 + options['Inferior vena cava inlet angle over degrees'] = -30.0 + options['Inferior vena cava inlet derivative factor'] = 1.0 + options['Inferior vena cava inlet length'] = 0.1 + options['Inferior vena cava inlet inner diameter'] = 0.22 + options['Inferior vena cava inlet wall thickness'] = 0.015 + options['Superior vena cava inlet position over'] = 0.65 + options['Superior vena cava inlet position right'] = 0.2 + options['Superior vena cava inlet angle left degrees'] = -10.0 + options['Superior vena cava inlet angle over degrees'] = -5.0 + options['Superior vena cava inlet derivative factor'] = 1.0 + options['Superior vena cava inlet length'] = 0.1 + options['Superior vena cava inlet inner diameter'] = 0.18 + options['Superior vena cava inlet wall thickness'] = 0.015 + options['Refine'] = False + options['Refine number of elements surface'] = 4 + options['Refine number of elements through wall'] = 1 + options['Use cross derivatives'] = False + + if isHuman: + if notUnitScale: options['Unit scale'] = 80.0 - elif 'Mouse' in parameterSetName: - if 'Unit' not in parameterSetName: - options['Unit scale'] = 5.0 - options['Number of left pulmonary veins'] = 1 - options['Left pulmonary vein angle up degrees'] = 30.0 - options['Left pulmonary vein inner diameter'] = 0.16 - options['Left pulmonary vein wall thickness'] = 0.011 - options['Right pulmonary vein angle up degrees'] = 10.0 - options['Right pulmonary vein inner diameter'] = 0.15 - options['Right pulmonary vein wall thickness'] = 0.011 - options['Superior vena cava inner diameter'] = 0.17 - options['Superior vena cava wall thickness'] = 0.012 + elif isMouse or isRat: + if notUnitScale: + options['Unit scale'] = 5.0 if isMouse else 12.0 + options['Atria outer height'] = 0.4 + options['Atrial base front incline degrees'] = 10.0 + options['Atria base inner major axis length'] = 0.42 + options['Atria base inner minor axis length'] = 0.35 + options['Atrial septum height'] = 0.27 + options['Atrial septum length'] = 0.22 + options['Fossa ovalis height'] = 0.08 + options['Fossa ovalis length'] = 0.12 + options['Fossa ovalis midpoint height'] = 0.18 + options['Atrial base wall thickness'] = 0.06 + options['Fossa ovalis thickness'] = options['Atrial septum thickness'] + options['Atrial vestibule outer height'] = 0.1 + options['Atrial base back incline degrees'] = 0.0 + options['Atria venous midpoint over'] = 0.45 + options['Atria venous anterior over'] = 0.75 + options['Left atrial appendage angle axial degrees'] = -15.0 + options['Left atrial appendage angle left degrees'] = 20.0 + options['Left atrial appendage angle up degrees'] = -65.0 + options['Left atrial appendage arc length'] = 0.6 + options['Left atrial appendage arc radius'] = 0.2 + options['Left atrial appendage base length'] = 0.3 + options['Left atrial appendage left'] = 0.9 + options['Left atrial appendage midpoint left'] = 0.55 + options['Left atrial appendage midpoint over'] = 1.0 + options['Left atrial appendage wall thickness'] = 0.025 + options['Left atrial appendage wedge angle degrees'] = 60.0 + options['Right atrial appendage angle axial degrees'] = 22.0 + options['Right atrial appendage angle left degrees'] = -10.0 + options['Right atrial appendage angle up degrees'] = -40.0 + options['Right atrial appendage arc length'] = 0.6 + options['Right atrial appendage arc radius'] = 0.2 + options['Right atrial appendage base length'] = 0.3 + options['Right atrial appendage midpoint right'] = 0.55 + options['Right atrial appendage midpoint over'] = 0.97 + options['Right atrial appendage pouch right'] = 1.0 + options['Right atrial appendage wall thickness'] = 0.025 + options['Right atrial appendage wedge angle degrees'] = 50.0 + options['Common left-right pulmonary vein ostium'] = True + options['Left pulmonary vein ostium angle degrees'] = 15.0 + options['Left pulmonary vein ostium position left'] = 0.34 + options['Left pulmonary vein ostium position over'] = 0.45 + options['Inferior vena cava inlet position over'] = 0.24 + options['Inferior vena cava inlet position right'] = 0.2 + options['Inferior vena cava inlet angle left degrees'] = 0.0 + options['Inferior vena cava inlet angle over degrees'] = -20.0 + options['Inferior vena cava inlet derivative factor'] = 1.0 + options['Inferior vena cava inlet length'] = 0.12 + options['Inferior vena cava inlet inner diameter'] = 0.16 + options['Inferior vena cava inlet wall thickness'] = 0.011 + options['Superior vena cava inlet position over'] = 0.62 + options['Superior vena cava inlet position right'] = 0.18 + options['Superior vena cava inlet angle left degrees'] = 0.0 + options['Superior vena cava inlet angle over degrees'] = -10.0 + options['Superior vena cava inlet derivative factor'] = 1.0 + options['Superior vena cava inlet length'] = 0.12 + options['Superior vena cava inlet inner diameter'] = 0.14 + options['Superior vena cava inlet wall thickness'] = 0.01 elif 'Pig' in parameterSetName: - if 'Unit' not in parameterSetName: + if notUnitScale: options['Unit scale'] = 80.0 - options['Number of left pulmonary veins'] = 1 - options['Number of right pulmonary veins'] = 1 - options['Left pulmonary vein inner diameter'] = 0.16 - options['Left pulmonary vein wall thickness'] = 0.011 - options['Right pulmonary vein inner diameter'] = 0.17 - options['Right pulmonary vein wall thickness'] = 0.011 - options['Inferior vena cava angle left degrees'] = 50.0 - options['Superior vena cava angle up degrees'] = 30.0 - elif 'Rat' in parameterSetName: - if 'Unit' not in parameterSetName: - options['Unit scale'] = 12.0 - options['Number of left pulmonary veins'] = 1 - options['Left pulmonary vein angle up degrees'] = 30.0 - options['Left pulmonary vein inner diameter'] = 0.16 - options['Left pulmonary vein wall thickness'] = 0.011 - options['Right pulmonary vein angle up degrees'] = 10.0 - options['Right pulmonary vein inner diameter'] = 0.15 - options['Right pulmonary vein wall thickness'] = 0.011 - options['Superior vena cava inner diameter'] = 0.17 - options['Superior vena cava wall thickness'] = 0.012 + options['Atrial base side incline degrees'] = 0.0 + options['Left atrial appendage angle axial degrees'] = -10.0 + options['Left atrial appendage angle left degrees'] = 20.0 + options['Left atrial appendage angle up degrees'] = -60.0 + options['Left atrial appendage arc length'] = 0.4 + options['Left atrial appendage arc radius'] = 0.3 + options['Left atrial appendage base length'] = 0.35 + options['Left atrial appendage left'] = 0.9 + options['Left atrial appendage midpoint left'] = 0.55 + options['Left atrial appendage midpoint over'] = 1.0 + options['Left atrial appendage wedge angle degrees'] = 50.0 + options['Right atrial appendage angle axial degrees'] = 10.0 + options['Right atrial appendage angle left degrees'] = -20.0 + options['Right atrial appendage angle up degrees'] = -10.0 + options['Right atrial appendage arc length'] = 0.5 + options['Right atrial appendage arc radius'] = 0.25 + options['Right atrial appendage base length'] = 0.25 + options['Right atrial appendage midpoint right'] = 0.55 + options['Right atrial appendage wedge angle degrees'] = 60.0 + options['Right pulmonary vein ostium angle degrees'] = 80.0 + options['Right pulmonary vein ostium position left'] = 0.22 + options['Right pulmonary vein ostium position over'] = 0.33 + options['Inferior vena cava inlet position over'] = 0.22 + options['Inferior vena cava inlet position right'] = 0.2 + options['Inferior vena cava inlet angle left degrees'] = 10.0 + options['Inferior vena cava inlet angle over degrees'] = 0.0 + options['Inferior vena cava inlet derivative factor'] = 0.5 + options['Superior vena cava inlet position over'] = 0.6 + options['Superior vena cava inlet angle left degrees'] = -15.0 + options['Superior vena cava inlet angle over degrees'] = -10.0 + cls.updateSubScaffoldOptions(options) return options @staticmethod def getOrderedOptionNames(): return [ - 'Number of elements around atrial free wall', 'Number of elements around atrial septum', - 'Number of elements up atria', - 'Number of elements inlet', + 'Number of elements around left atrium free wall', + 'Number of elements around right atrium free wall', + 'Number of elements over atria', 'Unit scale', 'Atria base inner major axis length', 'Atria base inner minor axis length', 'Atria major axis rotation degrees', 'Atria outer height', + 'Atrial septum height', + 'Atrial septum length', 'Atrial septum thickness', - 'Atrial free wall thickness', + 'Atrial vestibule outer height', + 'Fossa ovalis height', + 'Fossa ovalis length', + 'Fossa ovalis midpoint height', + 'Fossa ovalis thickness', + 'Left atrium venous free wall thickness', + 'Right atrium venous free wall thickness', + 'Crista terminalis thickness', 'Atrial base wall thickness', 'Atrial base slope degrees', 'Aorta outer plus diameter', 'Atrial base front incline degrees', 'Atrial base back incline degrees', 'Atrial base side incline degrees', - 'Atrial element size ratio anterior/posterior', - 'Number of left pulmonary veins', - 'Left pulmonary vein position up', - 'Left pulmonary vein angle up degrees', - 'Left pulmonary vein length factor', - 'Left pulmonary vein inner diameter', - 'Left pulmonary vein wall thickness', - 'Number of right pulmonary veins', - 'Right pulmonary vein position up', - 'Right pulmonary vein angle up degrees', - 'Right pulmonary vein length factor', - 'Right pulmonary vein inner diameter', - 'Right pulmonary vein wall thickness', - 'Inferior vena cava position up', - 'Inferior vena cava angle up degrees', - 'Inferior vena cava angle left degrees', - 'Inferior vena cava length factor', - 'Inferior vena cava inner diameter', - 'Inferior vena cava wall thickness', - 'Superior vena cava position up', - 'Superior vena cava angle up degrees', - 'Superior vena cava length factor', - 'Superior vena cava inner diameter', - 'Superior vena cava wall thickness', + 'Atria venous anterior over', + 'Atria venous midpoint over', + 'Left atrium venous midpoint left', + 'Right atrium venous right', + 'Left atrial appendage angle axial degrees', + 'Left atrial appendage angle left degrees', + 'Left atrial appendage angle up degrees', + 'Left atrial appendage arc length', + 'Left atrial appendage arc radius', + 'Left atrial appendage base length', + 'Left atrial appendage left', + 'Left atrial appendage midpoint left', + 'Left atrial appendage midpoint over', + 'Left atrial appendage wall thickness', + 'Left atrial appendage wedge angle degrees', + 'Right atrial appendage angle axial degrees', + 'Right atrial appendage angle left degrees', + 'Right atrial appendage angle up degrees', + 'Right atrial appendage arc length', + 'Right atrial appendage arc radius', + 'Right atrial appendage base length', + 'Right atrial appendage midpoint right', + 'Right atrial appendage midpoint over', + 'Right atrial appendage pouch right', + 'Right atrial appendage wall thickness', + 'Right atrial appendage wedge angle degrees', + 'Common left-right pulmonary vein ostium', + 'Left pulmonary vein ostium', + 'Left pulmonary vein ostium angle degrees', + 'Left pulmonary vein ostium position left', + 'Left pulmonary vein ostium position over', + 'Right pulmonary vein ostium', + 'Right pulmonary vein ostium angle degrees', + 'Right pulmonary vein ostium position left', + 'Right pulmonary vein ostium position over', + 'Inferior vena cava inlet position over', + 'Inferior vena cava inlet position right', + 'Inferior vena cava inlet angle left degrees', + 'Inferior vena cava inlet angle over degrees', + 'Inferior vena cava inlet derivative factor', + 'Inferior vena cava inlet length', + 'Inferior vena cava inlet inner diameter', + 'Inferior vena cava inlet wall thickness', + 'Superior vena cava inlet position over', + 'Superior vena cava inlet position right', + 'Superior vena cava inlet angle left degrees', + 'Superior vena cava inlet angle over degrees', + 'Superior vena cava inlet derivative factor', + 'Superior vena cava inlet length', + 'Superior vena cava inlet inner diameter', + 'Superior vena cava inlet wall thickness', 'Refine', 'Refine number of elements surface', 'Refine number of elements through wall', #,'Use cross derivatives' ] - @staticmethod - def checkOptions(options): + @classmethod + def getOptionValidScaffoldTypes(cls, optionName): + if optionName in [ 'Left pulmonary vein ostium', 'Right pulmonary vein ostium' ]: + return [ MeshType_3d_ostium1 ] + return [] + + @classmethod + def getOptionScaffoldTypeParameterSetNames(cls, optionName, scaffoldType): + if optionName == 'Left pulmonary vein ostium': + return list(cls.lpvOstiumDefaultScaffoldPackages.keys()) + if optionName == 'Right pulmonary vein ostium': + return list(cls.rpvOstiumDefaultScaffoldPackages.keys()) + assert scaffoldType in cls.getOptionValidScaffoldTypes(optionName), cls.__name__ + '.getOptionScaffoldTypeParameterSetNames. ' + \ + 'Invalid option \'' + optionName + '\' scaffold type ' + scaffoldType.getName() + return scaffoldType.getParameterSetNames() + + @classmethod + def getOptionScaffoldPackage(cls, optionName, scaffoldType, parameterSetName=None): + ''' + :param parameterSetName: Name of valid parameter set for option Scaffold, or None for default. + :return: ScaffoldPackage. + ''' + if parameterSetName: + assert parameterSetName in cls.getOptionScaffoldTypeParameterSetNames(optionName, scaffoldType), \ + 'Invalid parameter set ' + str(parameterSetName) + ' for scaffold ' + str(scaffoldType.getName()) + ' in option ' + str(optionName) + ' of scaffold ' + cls.getName() + if optionName == 'Left pulmonary vein ostium': + if not parameterSetName: + parameterSetName = list(cls.lpvOstiumDefaultScaffoldPackages.keys())[0] + return copy.deepcopy(cls.lpvOstiumDefaultScaffoldPackages[parameterSetName]) + if optionName == 'Right pulmonary vein ostium': + if not parameterSetName: + parameterSetName = list(cls.rpvOstiumDefaultScaffoldPackages.keys())[0] + return copy.deepcopy(cls.rpvOstiumDefaultScaffoldPackages[parameterSetName]) + assert False, cls.__name__ + '.getOptionScaffoldPackage: Option ' + optionName + ' is not a scaffold' + + @classmethod + def checkOptions(cls, options): ''' :return: True if dependent options changed, otherwise False. ''' dependentChanges = False - if options['Number of elements around atrial free wall'] < 6: - options['Number of elements around atrial free wall'] = 6 - # need even number of elements around free wall - if (options['Number of elements around atrial free wall'] % 2) == 1: - options['Number of elements around atrial free wall'] += 1 if options['Number of elements around atrial septum'] < 2: options['Number of elements around atrial septum'] = 2 - if options['Number of elements up atria'] < 3: - options['Number of elements up atria'] = 3 - if options['Number of elements inlet'] < 1: - options['Number of elements inlet'] = 1 + for key in [ + 'Number of elements around left atrium free wall', + 'Number of elements around right atrium free wall']: + if options[key] < 6: + options[key] = 6 + elif options[key] > 10: + options[key] = 10 + for key in ['Number of elements over atria']: + if options[key] < 6: + options[key] = 6 + elif options[key] > 6: + options[key] = 8 for key in [ 'Unit scale', 'Atria base inner major axis length', 'Atria base inner minor axis length', 'Atria outer height', + 'Atrial septum height', + 'Atrial septum length', 'Atrial septum thickness', - 'Atrial free wall thickness', + 'Atrial vestibule outer height', + 'Fossa ovalis height', + 'Fossa ovalis length', + 'Fossa ovalis thickness', + 'Fossa ovalis midpoint height', + 'Left atrium venous free wall thickness', + 'Right atrium venous free wall thickness', + 'Crista terminalis thickness', 'Atrial base wall thickness', 'Atrial base slope degrees', - 'Left pulmonary vein length factor', - 'Left pulmonary vein inner diameter', - 'Left pulmonary vein wall thickness', - 'Right pulmonary vein length factor', - 'Right pulmonary vein inner diameter', - 'Right pulmonary vein wall thickness', - 'Inferior vena cava length factor', - 'Inferior vena cava inner diameter', - 'Inferior vena cava wall thickness', - 'Superior vena cava length factor', - 'Superior vena cava inner diameter', - 'Superior vena cava wall thickness']: + 'Left atrial appendage arc length', + 'Left atrial appendage base length', + 'Left atrial appendage wall thickness', + 'Right atrial appendage arc length', + 'Right atrial appendage base length', + 'Right atrial appendage wall thickness', + 'Inferior vena cava inlet derivative factor', + 'Inferior vena cava inlet length', + 'Inferior vena cava inlet inner diameter', + 'Inferior vena cava inlet wall thickness', + 'Superior vena cava inlet derivative factor', + 'Superior vena cava inlet length', + 'Superior vena cava inlet inner diameter', + 'Superior vena cava inlet wall thickness']: if options[key] < 0.0: options[key] = 0.0 - if options['Atrial element size ratio anterior/posterior'] < 0.1: - options['Atrial element size ratio anterior/posterior'] = 0.1 - elif options['Atrial element size ratio anterior/posterior'] > 10.0: - options['Atrial element size ratio anterior/posterior'] = 10.0 for key in [ - 'Number of left pulmonary veins', - 'Number of right pulmonary veins']: - if options[key] < 1: - options[key] = 1 - elif options[key] > 2: - options[key] = 2 + 'Atria venous anterior over', + 'Atria venous midpoint over', + 'Left atrium venous midpoint left', + 'Right atrium venous right', + 'Left atrial appendage left', + 'Left atrial appendage midpoint left', + 'Left atrial appendage midpoint over', + 'Right atrial appendage midpoint right', + 'Right atrial appendage midpoint over', + 'Left pulmonary vein ostium position left', + 'Left pulmonary vein ostium position over', + 'Right pulmonary vein ostium position left', + 'Right pulmonary vein ostium position over', + 'Inferior vena cava inlet position over', + 'Inferior vena cava inlet position right', + 'Superior vena cava inlet position over', + 'Superior vena cava inlet position right']: + if options[key] < 0.0: + options[key] = 0.0 + elif options[key] > 1.0: + options[key] = 1.0 for key in [ - 'Left pulmonary vein position up', - 'Right pulmonary vein position up', - 'Inferior vena cava position up', - 'Superior vena cava position up']: - if options[key] < 0.1: - options[key] = 0.1 - elif options[key] > 0.9: - options[key] = 0.9 + 'Right atrial appendage pouch right']: + if options[key] < 0.0: + options[key] = 0.0 + elif options[key] > 2.0: + options[key] = 2.0 if options['Aorta outer plus diameter'] < options['Atrial septum thickness']: options['Aorta outer plus diameter'] = options['Atrial septum thickness'] for key in [ @@ -247,13 +588,58 @@ def checkOptions(options): options[key] = -75.0 elif options[key] > 75.0: options[key] = 75.0 + for key in [ + 'Left atrial appendage arc radius', + 'Right atrial appendage arc radius']: + if options[key] < 0.1*math.fabs(options['Atria base inner minor axis length']): + options[key] = 0.1*math.fabs(options['Atria base inner minor axis length']) + dependentChanges = True + elif options[key] > 1000.0*math.fabs(options['Atria base inner major axis length']): + options[key] = 1000.0*math.fabs(options['Atria base inner major axis length']) + dependentChanges = True + for key in [ + 'Left atrial appendage wedge angle degrees', + 'Right atrial appendage wedge angle degrees']: + if options[key] < 5.0: + options[key] = 5.0 + elif options[key] > 150.0: + options[key] = 150.0 for key in [ 'Refine number of elements surface', 'Refine number of elements through wall']: if options[key] < 1: options[key] = 1 + cls.updateSubScaffoldOptions(options) return dependentChanges + @classmethod + def updateSubScaffoldOptions(cls, options): + ''' + Update lpv, rpv ostium sub-scaffold options which depend on parent options. + ''' + elementsCountAroundLeftAtriumFreeWall = options['Number of elements around left atrium free wall'] + elementsCountOverAtria = options['Number of elements over atria'] + unitScale = options['Unit scale'] + laVenousFreeWallThickness = options['Left atrium venous free wall thickness'] # not scaled by unitScale + commonLeftRightPvOstium = options['Common left-right pulmonary vein ostium'] + lpvOstium = options['Left pulmonary vein ostium'] + rpvOstium = options['Right pulmonary vein ostium'] + elementsCountAroundLpvOstium, elementsCountAroundRpvOstium = \ + getLeftAtriumPulmonaryVeinOstiaElementsCounts(elementsCountAroundLeftAtriumFreeWall, elementsCountOverAtria, commonLeftRightPvOstium) + lpvOstiumSettings = lpvOstium.getScaffoldSettings() + lpvOstiumSettings['Number of elements around ostium'] = elementsCountAroundLpvOstium + lpvOstiumSettings['Unit scale'] = unitScale + lpvOstiumSettings['Ostium wall thickness'] = laVenousFreeWallThickness + lpvOstiumSettings['Outlet'] = False + lpvOstiumSettings['Use linear through ostium wall'] = False + rpvOstiumSettings = rpvOstium.getScaffoldSettings() + rpvOstiumSettings['Number of elements around ostium'] = elementsCountAroundRpvOstium + rpvOstiumSettings['Unit scale'] = unitScale + rpvOstiumSettings['Ostium wall thickness'] = laVenousFreeWallThickness + rpvOstiumSettings['Outlet'] = False + rpvOstiumSettings['Use linear through ostium wall'] = False + + @classmethod def generateBaseMesh(cls, region, options): """ @@ -262,11 +648,12 @@ def generateBaseMesh(cls, region, options): :param options: Dict containing options. See getDefaultOptions(). :return: list of AnnotationGroup """ - elementsCountAroundAtrialFreeWall = options['Number of elements around atrial free wall'] + cls.updateSubScaffoldOptions(options) elementsCountAroundAtrialSeptum = options['Number of elements around atrial septum'] - elementsCountAroundAtria = elementsCountAroundAtrialFreeWall + elementsCountAroundAtrialSeptum - elementsCountUpAtria = options['Number of elements up atria'] - elementsCountInlet = options['Number of elements inlet'] + elementsCountAroundLeftAtriumFreeWall = options['Number of elements around left atrium free wall'] + elementsCountAroundLeftAtrium = elementsCountAroundLeftAtriumFreeWall + elementsCountAroundAtrialSeptum + elementsCountAroundRightAtriumFreeWall = options['Number of elements around right atrium free wall'] + elementsCountOverAtria = options['Number of elements over atria'] unitScale = options['Unit scale'] aBaseInnerMajorMag = unitScale*0.5*options['Atria base inner major axis length'] @@ -277,34 +664,70 @@ def generateBaseMesh(cls, region, options): aBaseFrontInclineRadians = math.radians(options['Atrial base front incline degrees']) aBaseSideInclineRadians = math.radians(options['Atrial base side incline degrees']) aBaseBackInclineRadians = math.radians(options['Atrial base back incline degrees']) - aElementSizeRatioAnteriorPosterior = options['Atrial element size ratio anterior/posterior'] + aSeptumHeight = unitScale*options['Atrial septum height'] + aSeptumLength = unitScale*options['Atrial septum length'] aSeptumThickness = unitScale*options['Atrial septum thickness'] - aFreeWallThickness = unitScale*options['Atrial free wall thickness'] + aVestibuleOuterHeight = unitScale*options['Atrial vestibule outer height'] + foMidpointZ = unitScale*options['Fossa ovalis midpoint height'] + foMagZ = unitScale*0.5*options['Fossa ovalis height'] + foMagY = unitScale*0.5*options['Fossa ovalis length'] + foThickness = unitScale*options['Fossa ovalis thickness'] + laVenousFreeWallThickness = unitScale*options['Left atrium venous free wall thickness'] + raVenousFreeWallThickness = unitScale*options['Right atrium venous free wall thickness'] + cristaTerminalisThickness = unitScale*options['Crista terminalis thickness'] aBaseWallThickness = unitScale*options['Atrial base wall thickness'] aBaseSlopeRadians = math.radians(options['Atrial base slope degrees']) - lpvCount = options['Number of left pulmonary veins'] - lpvPositionUp = options['Left pulmonary vein position up'] - lpvAngleUpRadians = math.radians(options['Left pulmonary vein angle up degrees']) - lpvLengthFactor = options['Left pulmonary vein length factor'] - lpvInnerRadius = unitScale*0.5*options['Left pulmonary vein inner diameter'] - lpvWallThickness = unitScale*options['Left pulmonary vein wall thickness'] - rpvCount = options['Number of right pulmonary veins'] - rpvPositionUp = options['Right pulmonary vein position up'] - rpvAngleUpRadians = math.radians(options['Right pulmonary vein angle up degrees']) - rpvLengthFactor = options['Right pulmonary vein length factor'] - rpvInnerRadius = unitScale*0.5*options['Right pulmonary vein inner diameter'] - rpvWallThickness = unitScale*options['Right pulmonary vein wall thickness'] - ivcPositionUp = options['Inferior vena cava position up'] - ivcAngleUpRadians = math.radians(options['Inferior vena cava angle up degrees']) - ivcAngleLeftRadians = math.radians(options['Inferior vena cava angle left degrees']) - ivcLengthFactor = options['Inferior vena cava length factor'] - ivcInnerRadius = unitScale*0.5*options['Inferior vena cava inner diameter'] - ivcWallThickness = unitScale*options['Inferior vena cava wall thickness'] - svcPositionUp = options['Superior vena cava position up'] - svcAngleUpRadians = math.radians(options['Superior vena cava angle up degrees']) - svcLengthFactor = options['Superior vena cava length factor'] - svcInnerRadius = unitScale*0.5*options['Superior vena cava inner diameter'] - svcWallThickness = unitScale*options['Superior vena cava wall thickness'] + aVenousAnteriorOver = options['Atria venous anterior over'] + aVenousMidpointOver = options['Atria venous midpoint over'] + laVenousMidpointLeft = options['Left atrium venous midpoint left'] + raVenousRight = options['Right atrium venous right'] + laaAngleAxialRadians = math.radians(options['Left atrial appendage angle axial degrees']) + laaAngleLeftRadians = math.radians(options['Left atrial appendage angle left degrees']) + laaAngleUpradians = math.radians(options['Left atrial appendage angle up degrees']) + laaArcLength = unitScale*options['Left atrial appendage arc length'] + laaArcRadius = unitScale*options['Left atrial appendage arc radius'] + laaBaseLength = unitScale*options['Left atrial appendage base length'] + laaLeft = options['Left atrial appendage left'] + laaMidpointLeft = options['Left atrial appendage midpoint left'] + laaMidpointOver = options['Left atrial appendage midpoint over'] + laaWallThickness = unitScale*options['Left atrial appendage wall thickness'] + laaWedgeAngleRadians = math.radians(options['Left atrial appendage wedge angle degrees']) + raaAngleAxialRadians = math.radians(options['Right atrial appendage angle axial degrees']) + raaAngleLeftRadians = math.radians(options['Right atrial appendage angle left degrees']) + raaAngleUpradians = math.radians(options['Right atrial appendage angle up degrees']) + raaArcLength = unitScale*options['Right atrial appendage arc length'] + raaArcRadius = unitScale*options['Right atrial appendage arc radius'] + raaBaseLength = unitScale*options['Right atrial appendage base length'] + raaMidpointRight = options['Right atrial appendage midpoint right'] + raaMidpointOver = options['Right atrial appendage midpoint over'] + raaPouchRight = options['Right atrial appendage pouch right'] + raaWallThickness = unitScale*options['Right atrial appendage wall thickness'] + raaWedgeAngleRadians = math.radians(options['Right atrial appendage wedge angle degrees']) + commonLeftRightPvOstium = options['Common left-right pulmonary vein ostium'] + lpvOstium = options['Left pulmonary vein ostium'] + lpvOstiumAngleRadians = math.radians(options['Left pulmonary vein ostium angle degrees']) + lpvOstiumPositionLeft = options['Left pulmonary vein ostium position left'] + lpvOstiumPositionOver = options['Left pulmonary vein ostium position over'] + rpvOstium = options['Right pulmonary vein ostium'] + rpvOstiumAngleRadians = math.radians(options['Right pulmonary vein ostium angle degrees']) + rpvOstiumPositionLeft = options['Right pulmonary vein ostium position left'] + rpvOstiumPositionOver = options['Right pulmonary vein ostium position over'] + ivcPositionOver = options['Inferior vena cava inlet position over'] + ivcPositionRight = options['Inferior vena cava inlet position right'] + ivcAngleLeftRadians = math.radians(options['Inferior vena cava inlet angle left degrees']) + ivcAngleOverRadians = math.radians(options['Inferior vena cava inlet angle over degrees']) + ivcDerivativeFactor = options['Inferior vena cava inlet derivative factor'] + ivcLength = unitScale*options['Inferior vena cava inlet length'] + ivcInnerRadius = unitScale*0.5*options['Inferior vena cava inlet inner diameter'] + ivcWallThickness = unitScale*options['Inferior vena cava inlet wall thickness'] + svcPositionOver = options['Superior vena cava inlet position over'] + svcPositionRight = options['Superior vena cava inlet position right'] + svcAngleLeftRadians = math.radians(options['Superior vena cava inlet angle left degrees']) + svcAngleOverRadians = math.radians(options['Superior vena cava inlet angle over degrees']) + svcDerivativeFactor = options['Superior vena cava inlet derivative factor'] + svcLength = unitScale*options['Superior vena cava inlet length'] + svcInnerRadius = unitScale*0.5*options['Superior vena cava inlet inner diameter'] + svcWallThickness = unitScale*options['Superior vena cava inlet wall thickness'] useCrossDerivatives = options['Use cross derivatives'] fm = region.getFieldmodule() @@ -316,13 +739,58 @@ def generateBaseMesh(cls, region, options): raGroup = AnnotationGroup(region, 'right atrium', FMANumber = 7096, lyphID = 'Lyph ID unknown') aSeptumGroup = AnnotationGroup(region, 'interatrial septum', FMANumber = 7108, lyphID = 'Lyph ID unknown') fossaGroup = AnnotationGroup(region, 'fossa ovalis', FMANumber = 9246, lyphID = 'Lyph ID unknown') - lipvGroup = AnnotationGroup(region, 'left inferior pulmonary vein', FMANumber = 49913, lyphID = 'Lyph ID unknown') - lspvGroup = AnnotationGroup(region, 'left superior pulmonary vein', FMANumber = 49916, lyphID = 'Lyph ID unknown') - ripvGroup = AnnotationGroup(region, 'right inferior pulmonary vein', FMANumber = 49911, lyphID = 'Lyph ID unknown') - rspvGroup = AnnotationGroup(region, 'right superior pulmonary vein', FMANumber = 49914, lyphID = 'Lyph ID unknown') + laaGroup = AnnotationGroup(region, 'left atrial appendage', FMANumber = 7219, lyphID = 'Lyph ID unknown') + raaGroup = AnnotationGroup(region, 'right atrial appendage', FMANumber = 7218, lyphID = 'Lyph ID unknown') + annotationGroups = [ laGroup, raGroup, aSeptumGroup, fossaGroup, laaGroup, raaGroup ] + + lpvOstiumSettings = lpvOstium.getScaffoldSettings() + lpvCount = lpvOstiumSettings['Number of vessels'] + if commonLeftRightPvOstium: + # use only lpv: + if lpvCount == 1: + pvGroup = AnnotationGroup(region, 'pulmonary vein', FMANumber = 'FMA number unknown', lyphID = 'Lyph ID unknown') + lpvGroups = [ pvGroup ] + else: + lpvGroup = AnnotationGroup(region, 'left pulmonary vein', FMANumber = 'FMA number unknown', lyphID = 'Lyph ID unknown') + if lpvCount == 2: + rpvGroup = AnnotationGroup(region, 'right pulmonary vein', FMANumber = 'FMA number unknown', lyphID = 'Lyph ID unknown') + lpvGroups = [ lpvGroup, rpvGroup ] + else: + rspvGroup = AnnotationGroup(region, 'right superior pulmonary vein', FMANumber = 49914, lyphID = 'Lyph ID unknown') + ripvGroup = AnnotationGroup(region, 'right inferior pulmonary vein', FMANumber = 49911, lyphID = 'Lyph ID unknown') + lpvGroups = [ lpvGroup, rspvGroup, ripvGroup ] + annotationGroups += lpvGroups + else: # separate left and right pulmonary vein ostia + if lpvCount == 1: + lpvGroup = AnnotationGroup(region, 'left pulmonary vein', FMANumber = 'FMA number unknown', lyphID = 'Lyph ID unknown') + lpvGroups = [ lpvGroup ] + else: + lipvGroup = AnnotationGroup(region, 'left inferior pulmonary vein', FMANumber = 49913, lyphID = 'Lyph ID unknown') + lspvGroup = AnnotationGroup(region, 'left superior pulmonary vein', FMANumber = 49916, lyphID = 'Lyph ID unknown') + if lpvCount == 2: + lpvGroups = [ lipvGroup, lspvGroup ] + else: # lpvCount == 3: + lmpvGroup = AnnotationGroup(region, 'left middle pulmonary vein', FMANumber = 'FMA number unknown', lyphID = 'Lyph ID unknown') + lpvGroups = [ lipvGroup, lmpvGroup, lspvGroup ] + annotationGroups += lpvGroups + rpvOstiumSettings = rpvOstium.getScaffoldSettings() + rpvCount = rpvOstiumSettings['Number of vessels'] + if rpvCount == 1: + rpvGroup = AnnotationGroup(region, 'right pulmonary vein', FMANumber = 'FMA number unknown', lyphID = 'Lyph ID unknown') + rpvGroups = [ rpvGroup ] + else: + ripvGroup = AnnotationGroup(region, 'right inferior pulmonary vein', FMANumber = 49911, lyphID = 'Lyph ID unknown') + rspvGroup = AnnotationGroup(region, 'right superior pulmonary vein', FMANumber = 49914, lyphID = 'Lyph ID unknown') + if rpvCount == 2: + rpvGroups = [ ripvGroup, rspvGroup ] + else: # rpvCount == 3: + rmpvGroup = AnnotationGroup(region, 'right middle pulmonary vein', FMANumber = 'FMA number unknown', lyphID = 'Lyph ID unknown') + rpvGroups = [ ripvGroup, rmpvGroup, rspvGroup ] + annotationGroups += rpvGroups + ivcInletGroup = AnnotationGroup(region, 'inferior vena cava inlet', FMANumber = 10951, lyphID = 'Lyph ID unknown') svcInletGroup = AnnotationGroup(region, 'superior vena cava inlet', FMANumber = 4720, lyphID = 'Lyph ID unknown') - annotationGroups = [ laGroup, raGroup, aSeptumGroup, fossaGroup, lipvGroup, lspvGroup, ripvGroup, rspvGroup, ivcInletGroup, svcInletGroup ] + annotationGroups += [ ivcInletGroup, svcInletGroup ] # av boundary nodes are put in left and right fibrous ring groups only so they can be found by heart1 lFibrousRingGroup = AnnotationGroup(region, 'left fibrous ring', FMANumber = 77124, lyphID = 'Lyph ID unknown') rFibrousRingGroup = AnnotationGroup(region, 'right fibrous ring', FMANumber = 77125, lyphID = 'Lyph ID unknown') @@ -348,548 +816,1263 @@ def generateBaseMesh(cls, region, options): nodeIdentifier = max(1, zinc_utils.getMaximumNodeIdentifier(nodes) + 1) + mesh = fm.findMeshByDimension(3) + + elementIdentifier = max(1, zinc_utils.getMaximumElementIdentifier(mesh) + 1) + + laMeshGroup = laGroup.getMeshGroup(mesh) + raMeshGroup = raGroup.getMeshGroup(mesh) + aSeptumMeshGroup = aSeptumGroup.getMeshGroup(mesh) + fossaMeshGroup = fossaGroup.getMeshGroup(mesh) + laaMeshGroup = laaGroup.getMeshGroup(mesh) + raaMeshGroup = raaGroup.getMeshGroup(mesh) + lpvMeshGroups = [ inletGroup.getMeshGroup(mesh) for inletGroup in lpvGroups ] + if not commonLeftRightPvOstium: + rpvMeshGroups = [ inletGroup.getMeshGroup(mesh) for inletGroup in rpvGroups ] + ivcInletMeshGroup = ivcInletGroup.getMeshGroup(mesh) + svcInletMeshGroup = svcInletGroup.getMeshGroup(mesh) + + # get elements count over atria, around left and right free wall base, and around ostia + # note elementsCountOverAtriaCoronarySinus is assumed to be 1 + elementsCountOverAtriaCoronarySinus, \ + elementsCountOverLeftAtriumNonVenousAnterior, elementsCountOverLeftAtriumVenous, elementsCountOverLeftAtriumNonVenousPosterior, \ + elementsCountOverRightAtriumNonVenousAnterior, elementsCountOverRightAtriumVenous, elementsCountOverRightAtriumNonVenousPosterior \ + = getOverAtriaElementsCounts(elementsCountOverAtria) + elementsCountAroundLeftAtriumAorta, elementsCountAroundLeftAtrialAppendageBase, elementsCountAroundLeftAtriumLPV, elementsCountAroundLeftAtriumRPV \ + = getLeftAtriumBaseFreewallElementsCounts(elementsCountAroundLeftAtriumFreeWall) + elementsCountAroundRightAtriumPosteriorVenous, elementsCountAroundRightAtrialAppendagePlainBase, \ + elementsCountAroundRightAtrialAppendagePouchBase, elementsCountAroundRightAtriumAorta \ + = getRightAtriumBaseFreewallElementsCounts(elementsCountAroundRightAtriumFreeWall) + elementsCountAroundLpvOstium, elementsCountAroundRpvOstium = \ + getLeftAtriumPulmonaryVeinOstiaElementsCounts(elementsCountAroundLeftAtriumFreeWall, elementsCountOverAtria, commonLeftRightPvOstium) + if commonLeftRightPvOstium: + elementsCountOverSideLeftAtriumLPV = elementsCountAroundLeftAtriumLPV + 1 + else: + elementsCountOverSideLeftAtriumLPV = elementsCountAroundLeftAtriumLPV + + # GRC fudge factors: + aOuterSeptumHeight = 1.2*aSeptumHeight + iaGrooveDerivative = 0.25*aSeptumThickness + aBaseSlopeHeight = aBaseWallThickness*math.sin(aBaseSlopeRadians) aBaseSlopeLength = aBaseWallThickness*math.cos(aBaseSlopeRadians) aBaseOuterMajorMag = aBaseInnerMajorMag + aBaseSlopeLength aBaseOuterMinorMag = aBaseInnerMinorMag + aBaseSlopeLength - laCentre, laSeptumRadians, laBaseInnerx, laBaseInnerd1, laBaseInnerd2, laBaseOuterx, laBaseOuterd1, laBaseOuterd2 = \ - getLeftAtriumBasePoints(elementsCountAroundAtrialFreeWall, elementsCountAroundAtrialSeptum, + elementsCountAroundTrackSurface = 20 # must be even, twice number of elements along + elementsCountAcrossTrackSurface = 10 + labx, labd1, labd2, labd3, rabx, rabd1, rabd2, rabd3, ltBaseOuterx, ltBaseOuterd1, ltBaseOuterd2, aSeptumBaseCentre, laCentre, laSeptumRadians, = \ + getAtriumBasePoints(elementsCountAroundAtrialSeptum, elementsCountAroundLeftAtriumFreeWall, elementsCountAroundRightAtriumFreeWall, aBaseInnerMajorMag, aBaseInnerMinorMag, aMajorAxisRadians, - aBaseWallThickness, aBaseSlopeHeight, aBaseSlopeLength, aSeptumThickness, - aortaOuterPlusRadius, aBaseFrontInclineRadians, aBaseSideInclineRadians, aBaseBackInclineRadians) - - laInnerx = [ laBaseInnerx ] - laInnerd1 = [ laBaseInnerd1 ] - laInnerd2 = [ laBaseInnerd2 ] - laOuterx = [ laBaseOuterx ] - laOuterd1 = [ laBaseOuterd1 ] - laOuterd2 = [ laBaseOuterd2 ] - laInnerd3 = [ [ None ]*elementsCountAroundAtria ] - laOuterd3 = [ [ None ]*elementsCountAroundAtria ] - for n2 in range(elementsCountUpAtria): - for lav in (laInnerx, laInnerd1, laInnerd2, laInnerd3, laOuterx, laOuterd1, laOuterd2, laOuterd3): - lav.append([ None ]*elementsCountAroundAtria) - - # GRC fudge factors: - aOuterSeptumHeight = 0.85*aOuterHeight - iaGrooveDerivative = 0.25*aSeptumThickness - n1MidFreeWall = elementsCountAroundAtrialFreeWall//2 - elementsCountRidgeVenous = int(math.ceil(0.49*n1MidFreeWall)) # was 0.49 - elementsCountAroundEnd = elementsCountAroundAtrialFreeWall - 2*elementsCountRidgeVenous - ridgeVenousDistance = 0.5*aSeptumThickness + 1.2*(ivcInnerRadius + svcInnerRadius + 2*(ivcWallThickness + svcWallThickness)) - - # get ranges of nodes/elements to omit where inlets are - - elementsCountAcrossVC = elementsCountRidgeVenous - elementsCountUpIVC = int(math.ceil(0.65*elementsCountUpAtria)) - elementsCountAroundIVC = (elementsCountAcrossVC + elementsCountUpIVC)*2 - ivce1min = 0 - ivce1max = ivce1min + elementsCountAcrossVC - 1 - ivce2min = 0 - ivce2max = ivce2min + elementsCountUpIVC - 1 - elementsCountUpSVC = int(math.ceil(0.58*elementsCountUpAtria)) - elementsCountAroundSVC = (elementsCountAcrossVC + elementsCountUpSVC)*2 - svce1max = elementsCountAroundAtrialFreeWall - 1 - svce1min = svce1max - elementsCountAcrossVC + 1 - svce2max = elementsCountUpAtria - 1 - svce2min = svce2max - elementsCountUpSVC + 1 - - elementsCountUpLPV = int(math.ceil(0.66*elementsCountUpAtria)) - lipve2max = elementsCountUpAtria - 1 - lipve2min = lipve2max - elementsCountUpLPV + 1 - lspve2max = elementsCountUpAtria - 1 - lspve2min = lspve2max - elementsCountUpLPV + 1 - if lpvCount == 1: - # merge inferior/superior lpv so straddles end ridge - elementsCountAcrossLPV = elementsCountAroundEnd - lspve1min = lipve1min = elementsCountAroundAtrialFreeWall//2 - elementsCountAcrossLPV//2 - lspve1max = lipve1max = lipve1min + elementsCountAcrossLPV - 1 - else: # lpvCount == 2: - elementsCountAcrossLPV = elementsCountAroundEnd//2 - lipve1min = elementsCountAroundAtrialFreeWall//2 - lipve1max = lipve1min + elementsCountAcrossLPV - 1 - lspve1max = elementsCountAroundAtrialFreeWall//2 - 1 - lspve1min = lspve1max - elementsCountAcrossLPV + 1 - elementsCountAroundLPV = elementsCountAcrossLPV + elementsCountUpLPV*2 - - elementsCountAcrossRPV = elementsCountRidgeVenous # was math.ceil(0.33*n1MidFreeWall) - ripve1max = elementsCountAroundAtrialFreeWall - 1 - ripve1min = ripve1max - elementsCountAcrossRPV + 1 - rspve1min = 0 - rspve1max = rspve1min + elementsCountAcrossRPV - 1 - if rpvCount == 1: - # merge inferior/superior rpv so straddles top ridge - elementsCountUpRPV = int(math.ceil(0.66*elementsCountUpAtria)) - ripve2min = elementsCountUpAtria - (elementsCountUpRPV + 1)//2 - ripve2max = elementsCountUpAtria + elementsCountUpRPV//2 - 1 - rspve2min = elementsCountUpAtria*2 - ripve2max - 1 - rspve2max = elementsCountUpAtria*2 - ripve2min - 1 - else: # rpvCount == 2: - elementsCountUpRPV = int(math.ceil(0.49*elementsCountUpAtria)) - ripve2min = elementsCountUpAtria - elementsCountUpRPV - ripve2max = elementsCountUpAtria - 1 - rspve2min = elementsCountUpAtria - elementsCountUpRPV - rspve2max = elementsCountUpAtria - 1 - elementsCountAroundRPV = (elementsCountAcrossRPV + elementsCountUpRPV)*2 - - addInlets = True - - # get la ridge points from cubic functions from ax = septum groove centre to cx at edge of venous atrium, using dx = mid outer LV base - laSeptumBaseCentrex = [ - -0.5*aSeptumThickness, - laCentre[1] + aBaseInnerMajorMag*math.sin(-aMajorAxisRadians)*math.cos(laSeptumRadians) \ - + aBaseInnerMinorMag*math.cos(-aMajorAxisRadians)*math.sin(laSeptumRadians), - -aBaseSlopeHeight ] - ax = [ 0.0, laSeptumBaseCentrex[1], aOuterSeptumHeight ] - ad1 = [ -iaGrooveDerivative, 0.0, 0.0 ] - dx = laBaseOuterx[n1MidFreeWall] - dd1 = [ -d for d in laBaseOuterd2[n1MidFreeWall]] - # get point on venous peak - # GRC fudge factor - px, pd1 = interp.sampleCubicHermiteCurves([ ax, dx ], [ ad1, dd1 ], elementsCountOut = 2, lengthFractionStart = 0.4, arcLengthDerivatives = True)[0:2] - nx = [ ax, [ px[1][0], px[1][1], aOuterHeight ] ] - nd1 = interp.smoothCubicHermiteDerivativesLine(nx, [ ad1, [ pd1[1][0], pd1[1][1], 0.0 ] ], fixStartDerivative = True, fixEndDirection = True) - ex = nx[1] - ed1 = nd1[1] - xi = 0.4 - # bx = in-between point to get more curvature near septum - bx = interp.interpolateCubicHermite(ax, ad1, ex, ed1, xi) - bd1 = interp.interpolateCubicHermiteDerivative(ax, ad1, ex, ed1, xi) - # cx = limit of venous atrium on ridge - cx, cd1, ce, cxi= interp.getCubicHermiteCurvesPointAtArcDistance([ ax, bx, ex, dx ], [ ad1, bd1, ed1, dd1 ], ridgeVenousDistance) - if elementsCountRidgeVenous == 1: - rx = [ [ ax[0], ax[1], ax[2] ], [ cx[0], cx[1], cx[2] ] ] - rd1 = [ [ ad1[0], ad1[1], ad1[2] ], [ cd1[0], cd1[1], cd1[2] ] ] - else: - rx, rd1 = interp.sampleCubicHermiteCurves([ ax, bx, cx ], [ ad1, bd1, cd1 ], elementsCountRidgeVenous, - lengthFractionStart = 0.5, addLengthStart = 0.5*iaGrooveDerivative, - arcLengthDerivatives = True)[0:2] - - # get points on outside arch of "venous" left atrium, anterior and posterior - for na in range(elementsCountRidgeVenous + 1): - np = elementsCountAroundAtrialFreeWall - na - # sample arch from double cubic through anterior, ridge and posterior points - lx, ld2, le, lxi = interp.sampleCubicHermiteCurves( - [ laBaseOuterx[na], rx[na], laBaseOuterx[np] ], - [ laBaseOuterd2[na], [ -rd1[na][1], rd1[na][0], 0.0 ], [ -d for d in laBaseOuterd2[np]] ], - 2*elementsCountUpAtria, elementLengthStartEndRatio = aElementSizeRatioAnteriorPosterior, - arcLengthDerivatives = True)[0:4] - ld1 = interp.interpolateSampleLinear([ laBaseOuterd1[na], rd1[na], [ -d for d in laBaseOuterd1[np]] ], le, lxi) - - for noa in range(1, elementsCountUpAtria*2): - if noa <= elementsCountUpAtria: - laOuterx[noa][na] = lx[noa] - laOuterd1[noa][na] = ld1[noa] - laOuterd2[noa][na] = ld2[noa] - else: - nop = elementsCountUpAtria*2 - noa - laOuterx[nop][np] = lx[noa] - laOuterd1[nop][np] = [ -d for d in ld1[noa] ] - laOuterd2[nop][np] = [ -d for d in ld2[noa] ] - # fix scale of base derivative 2 on anterior and posterior - laBaseOuterd2[na] = ld2[0] - laBaseOuterd2[np] = [-d for d in ld2[2*elementsCountUpAtria] ] - - # add round quarter sphere in LA beyond "RA venous" zone - # get apex point at end of "venous" atria - vx = laOuterx[elementsCountUpAtria][elementsCountRidgeVenous] - vd1 = laOuterd2[elementsCountUpAtria][elementsCountRidgeVenous] - vd2 = [ -d for d in laOuterd1[elementsCountUpAtria][elementsCountRidgeVenous] ] - startDerivative1 = vector.magnitude(laBaseOuterd2[elementsCountRidgeVenous]) - startDerivative3 = vector.magnitude(laBaseOuterd2[elementsCountAroundAtrialFreeWall - elementsCountRidgeVenous]) - startDerivative2 = 0.666*startDerivative3 # GRC fudge factor - radiansPerElementAroundEnd = math.pi/elementsCountAroundEnd - for n in range(1, elementsCountAroundEnd): - n1 = elementsCountRidgeVenous + n - ax = laBaseOuterx[n1] - ad1 = laBaseOuterd1[n1] - ad2 = laBaseOuterd2[n1] - radiansAround = n*radiansPerElementAroundEnd + aBaseWallThickness, aBaseSlopeHeight, aBaseSlopeLength, aSeptumLength, aSeptumThickness, + aortaOuterPlusRadius, aBaseFrontInclineRadians, aBaseSideInclineRadians, aBaseBackInclineRadians, + laaLeft, laVenousMidpointLeft, raVenousRight, raaPouchRight, elementsCountAroundTrackSurface) + + laTrackSurface = getAtriumTrackSurface(elementsCountAroundTrackSurface, elementsCountAcrossTrackSurface, + ltBaseOuterx, ltBaseOuterd1, ltBaseOuterd2, aSeptumBaseCentre, aOuterHeight, aOuterSeptumHeight, iaGrooveDerivative) + raTrackSurface = laTrackSurface.createMirrorX() + + # need to create pulmonary vein ostia early because other derivatives are smoothed to fit them + + # create left pulmonary vein ostium + lpvOstiumPosition = laTrackSurface.createPositionProportion(lpvOstiumPositionOver, lpvOstiumPositionLeft) + # get absolute direction on surface corresponding to chosen angle + cx, cd1, cd2 = laTrackSurface.evaluateCoordinates(lpvOstiumPosition, derivatives = True) + td1, td2, td3 = calculate_surface_axes(cd1, cd2, [ 0.0, 1.0, 0.0 ]) + zAngleRadians = math.atan2(td1[0], -td2[0]) + #print('zAngleRadians',zAngleRadians) + cosAngle = math.cos(zAngleRadians + lpvOstiumAngleRadians) + sinAngle = math.sin(zAngleRadians + lpvOstiumAngleRadians) + lpvOstiumDirection = [ (cosAngle*-td2[c] + sinAngle*td1[c]) for c in range(3) ] + vesselMeshGroups = [ [ laMeshGroup, meshGroup ] for meshGroup in lpvMeshGroups ] + nodeIdentifier, elementIdentifier, (lpvox, lpvod1, lpvod2, lpvod3, lpvoNodeId, lpvoPositions) = \ + generateOstiumMesh(region, lpvOstiumSettings, laTrackSurface, lpvOstiumPosition, lpvOstiumDirection, nodeIdentifier, elementIdentifier, vesselMeshGroups) + + if not commonLeftRightPvOstium: + # create right pulmonary vein ostium + rpvOstiumPosition = laTrackSurface.createPositionProportion(rpvOstiumPositionOver, rpvOstiumPositionLeft) + # get absolute direction on surface corresponding to chosen angle + cx, cd1, cd2 = laTrackSurface.evaluateCoordinates(rpvOstiumPosition, derivatives = True) + td1, td2, td3 = calculate_surface_axes(cd1, cd2, [ 0.0, 1.0, 0.0 ]) + zAngleRadians = math.atan2(td1[0], -td2[0]) + #print('zAngleRadians',zAngleRadians) + cosAngle = math.cos(zAngleRadians + rpvOstiumAngleRadians) + sinAngle = math.sin(zAngleRadians + rpvOstiumAngleRadians) + rpvOstiumDirection = [ (cosAngle*-td2[c] + sinAngle*td1[c]) for c in range(3) ] + vesselMeshGroups = [ [ laMeshGroup, meshGroup ] for meshGroup in rpvMeshGroups ] + nodeIdentifier, elementIdentifier, (rpvox, rpvod1, rpvod2, rpvod3, rpvoNodeId, rpvoPositions) = \ + generateOstiumMesh(region, rpvOstiumSettings, laTrackSurface, rpvOstiumPosition, rpvOstiumDirection, nodeIdentifier, elementIdentifier, vesselMeshGroups) + + # get points over interatrial septum on exterior groove + agn1Mid = elementsCountOverRightAtriumNonVenousAnterior + elementsCountOverRightAtriumVenous//2 + # 1. go up vestibule height on posterior + nx = laTrackSurface.nx [:laTrackSurface.elementsCount1 + 1] + nd = laTrackSurface.nd1[:laTrackSurface.elementsCount1 + 1] + csx, csd2, e, xi = interp.getCubicHermiteCurvesPointAtArcDistance(nx, nd, aVestibuleOuterHeight) + lagcsProportion = (e + xi)/laTrackSurface.elementsCount1 + agLength = sum(interp.getCubicHermiteArcLength(nx[e], nd[e], nx[e + 1], nd[e + 1]) for e in range(laTrackSurface.elementsCount1)) + # arbitrarily set midpoint derivative to reduce element spacing to fit nearby RPV ostium + agMidpointDerivative = 0.75/elementsCountOverAtria # GRC fudge factor tweak was 1.0 + agx = [ labx [1][0] ] + agd1 = [ labd1[1][0] ] + agd2 = [ labd2[1][0] ] # rescale later + # add lengths over groove from anterior to posterior, intersecting aVenousAnteriorOver, aVenousMidpointOver, lagcsProportion + ragProportionLengths1 = interp.sampleCubicElementLengths(1.0 - aVenousAnteriorOver, elementsCountOverLeftAtriumNonVenousAnterior) + ragProportionLengths2 = interp.sampleCubicElementLengths(aVenousAnteriorOver - aVenousMidpointOver, elementsCountOverLeftAtriumVenous//2, \ + startDerivative = ragProportionLengths1[-1], endDerivative = agMidpointDerivative) + ragProportionLengths3 = interp.sampleCubicElementLengths(aVenousMidpointOver - lagcsProportion, \ + elementsCountOverLeftAtriumVenous//2 + elementsCountOverLeftAtriumNonVenousPosterior - elementsCountOverAtriaCoronarySinus, \ + startDerivative = ragProportionLengths2[-1], endDerivative = lagcsProportion) + ragProportionLengths = ragProportionLengths1 + ragProportionLengths2 + ragProportionLengths3 + [ lagcsProportion ] + # get d1 magnitudes over crest at posterior, middle and anterior/aorta + d1a = vector.magnitude(labd1[1][0]) + d1p = vector.magnitude(labd1[1][elementsCountAroundLeftAtriumFreeWall]) + d1m = 0.25*d1p # GRC fudge factor - not necessarily used to get trackSurface! + ragProportion = 0.0 + ragProportions = [ 0.0 ] + for e in range(elementsCountOverAtria - 1): + ragProportion += ragProportionLengths[e] + ragProportions.append(ragProportion) + trackPosition = raTrackSurface.createPositionProportion(ragProportion, 0.0) + x, d2 = raTrackSurface.evaluateCoordinates(trackPosition, derivatives = True)[0:2] + agx.append(x) + if ragProportion < 0.5: + d1s = d1a + d1f = d1m + xid1 = 2.0*ragProportion + else: + d1s = d1m + d1f = d1p + xid1 = 2.0*(ragProportion - 0.5) + f1, _, f3, _ = interp.getCubicHermiteBasis(xid1) + agd1.append([ -(f1*d1s + f3*d1f), 0.0, 0.0 ]) + agd2.append(vector.setMagnitude(d2, agLength*0.5*(ragProportionLengths[e] + ragProportionLengths[e + 1]))) + ragProportions.append(1.0) + laVenousLimitPosterior = ragProportionLengths[-1] + ragProportionLengths[-2] + # Get heights of elements on aorta up interatrial groove, for building venous limit curves + aoHeight1 = ragProportionLengths[0]*agLength + # fix vestibule top d2 magnitude + agd2[-1] = vector.setMagnitude(agd2[-1], aVestibuleOuterHeight) + # smooth d2 up to vestibule top + agd2 = interp.smoothCubicHermiteDerivativesLine(agx, agd2, fixAllDirections = True, fixEndDerivative = True) # , magnitudeScalingMode = interp.DerivativeScalingMode.HARMONIC_MEAN) + # GRC , magnitudeScalingMode = interp.DerivativeScalingMode.HARMONIC_MEAN) + # reverse derivative on posterior vestibule top + agd1[-1] = [ -d for d in agd1[-1] ] + agd2[-1] = [ -d for d in agd2[-1] ] + # add posterior crux point + agx .append(labx [1][elementsCountAroundLeftAtriumFreeWall]) + agd1.append(labd1[1][elementsCountAroundLeftAtriumFreeWall]) + agd2.append(vector.setMagnitude(labd2[1][elementsCountAroundLeftAtriumFreeWall], aVestibuleOuterHeight)) + agd3 = [ None ]*(elementsCountOverAtria + 1) # set later, using adjacent points + # first and last d3 are known: + agd3[0] = labd3[1][0] + agd3[-1] = labd3[1][elementsCountAroundLeftAtriumFreeWall] + # copy derivatives to labd2[1], rabd2[1] + rabd2[1][elementsCountAroundRightAtriumFreeWall] = labd2[1][0] = agd2[0] + rabd2[1][0] = labd2[1][elementsCountAroundLeftAtriumFreeWall] = agd2[-1] + + # start getting points on interatrial septum next to vestibule height, then septum "arch" + # need these to get fossa angles + halffoThickness = 0.5*foThickness + halfaSeptumThickness = 0.5*aSeptumThickness + xia = 0.35 # GRC fudge factor + coronarySinusHeightAnterior = (1.0 - xia)*aVestibuleOuterHeight + xia*foMidpointZ + aSeptumPosteriorY = aSeptumBaseCentre[1] - 0.5*aSeptumLength + aSeptumAnteriorY = aSeptumBaseCentre[1] + 0.5*aSeptumLength + x1 = [ 0.0, aSeptumPosteriorY, aVestibuleOuterHeight ] + d1 = [ 0.0, aSeptumLength, 0.0 ] + x2 = [ 0.0, aSeptumAnteriorY, coronarySinusHeightAnterior ] + d2 = interp.interpolateHermiteLagrangeDerivative(x1, d1, x2, 1.0) # GRC was d1 + asx, asd1 = interp.sampleCubicHermiteCurves([ x1, x2 ], [ d1, d2 ], elementsCountAroundAtrialSeptum, arcLengthDerivatives = True)[0:2] + + # get fossa ovalis points at centre and around + elementsCountAroundFossa = elementsCountOverAtria + elementsCountAroundAtrialSeptum - 2 + fossaPerimeterLength = getApproximateEllipsePerimeter(foMagY, foMagZ) + estElementSizeAroundFossa = fossaPerimeterLength/elementsCountAroundFossa + fossaInnerDerivativeRatio = 1.5 # GRC fudge factor + fossaOuterDerivativeRatio = 2.0 - fossaInnerDerivativeRatio + foMidpointY = aSeptumBaseCentre[1] + fossaRadiansAround = [] + fox = [] + fod1 = [] + fod2 = [] + for nf in range(elementsCountAroundFossa): + if nf <= elementsCountAroundAtrialSeptum: + y = asx[nf][1] + z = asx[nf][2] + else: + na = nf - elementsCountAroundAtrialSeptum + 1 + y = agx[na][1] + z = agx[na][2] + radiansAround = getEllipseAngleFromVector(foMagY, foMagZ, y - foMidpointY, z - foMidpointZ) + #print('fossa y', y, 'z', z, 'angle', math.degrees(radiansAround)) + fossaRadiansAround.append(radiansAround) cosRadiansAround = math.cos(radiansAround) sinRadiansAround = math.sin(radiansAround) - bx = vx - bd1 = [ 0.5*(vd1[c]*sinRadiansAround - vd2[c]*cosRadiansAround) for c in range(3) ] - bd2 = [ (vd1[c]*cosRadiansAround + vd2[c]*sinRadiansAround) for c in range(3) ] - # quadratic Lagrange interpolation - xi = radiansAround/math.pi - phi1 = 1.0 + xi*(-3.0 + 2.0*xi) - phi2 = 4*xi*(1.0 - xi) - phi3 = xi*(-1.0 + 2.0*xi) - startDerivative = phi1*startDerivative1 + phi2*startDerivative2 + phi3*startDerivative3 - endDerivative = vector.magnitude(bd2) - ex, ed2, ee, exi = interp.sampleCubicHermiteCurves([ ax, bx ], [ ad2, bd2 ], elementsCountUpAtria, - addLengthStart = 0.5*startDerivative, lengthFractionStart = 0.5, - addLengthEnd = 0.5*endDerivative, lengthFractionEnd = 0.5, arcLengthDerivatives = True)[0:4] - ed1 = interp.interpolateSampleLinear([ ad1, bd1 ], ee, exi) - laOuterd2[0][n1] = ed2[0] - for n2 in range(1, elementsCountUpAtria): - laOuterx [n2][n1] = ex [n2] - laOuterd1[n2][n1] = ed1[n2] - laOuterd2[n2][n1] = ed2[n2] - - # smooth outer derivatives - n1Limit = (elementsCountAroundAtrialFreeWall + 1) - for n2 in range(1, elementsCountUpAtria): - sd1 = interp.smoothCubicHermiteDerivativesLine(laOuterx[n2][:n1Limit], laOuterd1[n2][:n1Limit], \ - fixStartDerivative = True, fixEndDerivative = True) - for n1 in range(1, n1Limit - 1): - laOuterd1[n2][n1] = sd1[n1] - # top row: - n2 = elementsCountUpAtria - n1Limit = elementsCountRidgeVenous + 1 - nx = laOuterx[n2][:n1Limit] - nx.append(laOuterx[n2 - 1][n1MidFreeWall]) - nd1 = laOuterd1[n2][:n1Limit] - nd1.append([ -d for d in laOuterd2[n2 - 1][n1MidFreeWall] ]) - sd1 = interp.smoothCubicHermiteDerivativesLine(nx, nd1, fixStartDerivative = True, fixEndDerivative = True) - for n1 in range(1, n1Limit): - laOuterd1[n2][n1] = sd1[n1] - + fox .append([ 0.0, foMidpointY + foMagY*cosRadiansAround, foMidpointZ + foMagZ*sinRadiansAround ]) + fod1.append(vector.setMagnitude([ 0.0, -foMagY*sinRadiansAround, foMagZ*cosRadiansAround ], estElementSizeAroundFossa)) + fod2.append([ 0.0, -fossaOuterDerivativeRatio*foMagY*cosRadiansAround, -fossaOuterDerivativeRatio*foMagZ*sinRadiansAround ]) + fod1 = interp.smoothCubicHermiteDerivativesLoop(fox, fod1, fixAllDirections = True) # GRC was , magnitudeScalingMode = interp.DerivativeScalingMode.HARMONIC_MEAN) + fossad3const = [ foThickness, 0.0, 0.0 ] + foCentrex = [] + foCentred1 = [] + foCentred2 = [] + foCentred3 = [] + fox = [ fox , copy.deepcopy(fox ) ] + fod1 = [ fod1, copy.deepcopy(fod1) ] + fod2 = [ fod2, copy.deepcopy(fod2) ] + fod3 = [ [ fossad3const ]*elementsCountAroundFossa ]*2 + for n3 in range(2): + fossaX = (-0.5 if (n3 == 0) else + 0.5)*foThickness + foCentrex.append([ fossaX, foMidpointY, foMidpointZ ]) + foCentred1.append([ 0.0, fossaInnerDerivativeRatio*foMagY, 0.0 ]) + foCentred2.append([ 0.0, 0.0, fossaInnerDerivativeRatio*foMagZ ]) + foCentred3.append(fossad3const) + for nf in range(elementsCountAroundFossa): + fox[n3][nf][0] = fossaX + + # complete getting points on interatrial septum at vestibule top, then septum "arch" + archMagY = 0.5*aSeptumLength + archMagZ = aSeptumHeight - foMidpointZ + halfArchEllipsePerimeter = 0.5*getApproximateEllipsePerimeter(archMagY, archMagZ) + archLength = (2.0*foMidpointZ - aVestibuleOuterHeight - coronarySinusHeightAnterior) + halfArchEllipsePerimeter + elementsCountOverArch = elementsCountOverAtria - 2 + estArchElementLength = archLength/elementsCountOverArch + asd2 = [ [ 0.0, 0.0, -estArchElementLength ] ] + for ns in range(1, elementsCountAroundAtrialSeptum): + nf = ns + # make d2 in normal d3, d1 + d2 = interp.interpolateLagrangeHermiteDerivative([ asx[ns][0] - halffoThickness, asx[ns][1], asx[ns][2] ], fox[0][nf], fod2[0][nf], 0.0) + d2 = vector.setMagnitude([ 0.0, -asd1[ns][2], asd1[ns][1] ], vector.magnitude(d2)) + d2 = interp.smoothCubicHermiteDerivativesLine([ [ asx[ns][0] - halffoThickness, asx[ns][1], asx[ns][2] ], fox[0][nf] ], [ d2, fod2[0][nf] ], + fixStartDirection = True, fixEndDerivative = True)[0] + asd2.append(d2) + asd2.append([ 0.0, 0.0, estArchElementLength ]) + # add points over arch: + archMagY = 0.5*aSeptumLength + archMagZ = aSeptumHeight - foMidpointZ + for na in range(1, elementsCountOverArch): + nf = elementsCountAroundAtrialSeptum + na + ng = na + 1 + #print('na', na, 'ng', ng, 'agx', agx[ng]) + if agx[ng][2] > foMidpointZ: + radiansAround = getEllipseAngleFromVector(archMagY, archMagZ, agx[ng][1] - foMidpointY, agx[ng][2] - foMidpointZ) + #print('arch y', agx[ng][1], 'z', agx[ng][2], 'angle', math.degrees(radiansAround)) + if radiansAround > 0.0: + cosRadiansAround = math.cos(radiansAround) + sinRadiansAround = math.sin(radiansAround) + x = [ 0.0, foMidpointY + archMagY*cosRadiansAround, foMidpointZ + archMagZ*sinRadiansAround ] + d2 = vector.setMagnitude([ 0.0, -archMagY*sinRadiansAround, archMagZ*cosRadiansAround ], estArchElementLength) + elif agx[ng][1] > foMidpointY: + xi = (aSeptumAnteriorY - foMidpointY)/(agx[ng][1] - foMidpointY) + x = [ 0.0, aSeptumAnteriorY, (1.0 - xi)*foMidpointZ + xi*agx[ng][2] ] + d2 = [ 0.0, 0.0, estArchElementLength ] + else: # agx[ng][1] > foMidpointY + xi = (aSeptumPosteriorY - foMidpointY)/(agx[ng][1] - foMidpointY) + x = [ 0.0, aSeptumPosteriorY, (1.0 - xi)*foMidpointZ + xi*agx[ng][2] ] + d2 = [ 0.0, 0.0, -estArchElementLength ] + # make d1 normal to d2, d3 + d1 = interp.interpolateHermiteLagrangeDerivative([ 0.0, fox[0][nf][1], fox[0][nf][2] ], [ -d for d in fod2[0][nf] ], x, 1.0) + d1 = vector.setMagnitude([ 0.0, d2[2], -d2[1] ], vector.magnitude(d1)) + d1 = interp.smoothCubicHermiteDerivativesLine([ [ 0.0, fox[0][nf][1], fox[0][nf][2] ], x ], [ [ -d for d in fod2[0][nf] ], d1 ], + fixStartDerivative = True, fixEndDirection = True)[1] + asx .append(x ) + asd1.append(d1) + asd2.append(d2) + asd2 = interp.smoothCubicHermiteDerivativesLoop(asx, asd2, fixAllDirections = True, magnitudeScalingMode = interp.DerivativeScalingMode.HARMONIC_MEAN) + # reverse first d2: + asd2[0] = [ -d for d in asd2[0] ] + # copy and displace for la, ra + asx = [ asx , copy.deepcopy(asx ) ] + asd1 = [ asd1 , copy.deepcopy(asd1) ] + asd2 = [ asd2 , copy.deepcopy(asd2) ] + septumd3const = [ aSeptumThickness, 0.0, 0.0 ] + septumd3minus = [ -aSeptumThickness, 0.0, 0.0 ] + asd3 = [ [ septumd3const ]*elementsCountAroundFossa, [ septumd3minus ]*elementsCountAroundFossa ] + for ns in range(elementsCountAroundFossa): + asx [0][ns][0] -= halfaSeptumThickness + asx [1][ns][0] += halfaSeptumThickness + asd1[1][ns] = [ -d for d in asd1[1][ns] ] + asd2[1][ns][0] = -asd2[1][ns][0] + # fix up derivative 2 on atrial septum base + for ns in range(elementsCountAroundAtrialSeptum + 1): + nl = -elementsCountAroundAtrialSeptum + ns + labd2[0][nl] = interp.interpolateLagrangeHermiteDerivative(labx[0][nl], [ asx[0][ns][0], asx[0][ns][1], asx[0][ns][2] ], asd2[0][ns], 0.0) + nr = -ns + rabd2[0][nr] = [ -labd2[0][nl][0], labd2[0][nl][1], labd2[0][nl][2] ] + # fix derivative 3 on interatrial groove + for na in range(elementsCountOverArch + 1): + ng = 1 + na + # this is a kludge, but looks alright: + agd3[ng] = vector.setMagnitude(vector.crossproduct3(agd1[ng], agd2[ng]), raVenousFreeWallThickness) + + # get points on external vestibule top, around entire free wall of both atria + # not all of these will become nodes, but they are always used to set base derivatives + # left atrium + lavtx = [ agx [1] ] + lavtd1 = [ agd1[1] ] + lavtd2 = [ agd2[1] ] + lavtd3 = [ agd3[1] ] + lan1Aorta = elementsCountAroundLeftAtriumAorta + lan1Mid = elementsCountAroundLeftAtriumAorta + elementsCountAroundLeftAtrialAppendageBase + lan1MidVenous = lan1Mid + elementsCountAroundLeftAtriumLPV + lavtProportions = [ [ 1.0 - ragProportions[1], 0.0 ] ] + laApexx = laTrackSurface.nx[-1] + for n1 in range(1, elementsCountAroundLeftAtriumFreeWall): + # find position on laTrackSurface corresponding to base outer node + # avoid point at end of laTrackSurface where derivative 1 is zero + onEnd = True + for c in range(3): + if math.fabs(labx[1][n1][c] - laApexx[c]) > 0.0001: + onEnd = False + break + if onEnd: + startPosition = laTrackSurface.createPositionProportion(0.5, 0.9999) + else: + if n1 <= lan1Mid: + startProportion1 = 1.0 + startProportion2 = laaLeft*n1/lan1Mid + else: + startProportion1 = 0.0 + startProportion2 = (elementsCountAroundLeftAtriumFreeWall - n1)/(elementsCountAroundLeftAtriumFreeWall - lan1Mid) + startPosition = laTrackSurface.findNearestPosition(labx[1][n1], laTrackSurface.createPositionProportion(startProportion1, startProportion2)) + onAorta = n1 == 1 + direction = [ 0.0, 0.0, 1.0 ] if onAorta else vector.normalise(labd2[1][n1]) + trackDistance1 = aoHeight1 if onAorta else aVestibuleOuterHeight + position = laTrackSurface.trackVector(startPosition, direction, trackDistance1) + lavtProportions.append(laTrackSurface.getProportion(position)) + x, d1, d2 = laTrackSurface.evaluateCoordinates(position, derivatives = True) + ax1, ax2, ax3 = calculate_surface_axes(d1, d2, direction) + lavtx .append(x) + lavtd1.append(vector.setMagnitude(ax2, -vector.magnitude(labd1[1][n1]))) + lavtd2.append(vector.setMagnitude(ax1, trackDistance1)) + lavtd3.append(vector.setMagnitude(ax3, laVenousFreeWallThickness)) + # fix d2 on outer base + labd2[1][n1] = vector.setMagnitude(labd2[1][n1], trackDistance1) + # add end points and smooth d1 + lavtx .append(agx [-2]) + lavtd1.append(agd1[-2]) + lavtd2.append(agd2[-2]) + lavtd3.append(agd3[-2]) + lavtProportions.append( [ 1.0 - ragProportions[-2], 0.0 ] ) + lavtd1 = interp.smoothCubicHermiteDerivativesLine(lavtx, lavtd1, fixAllDirections = True, fixStartDerivative = True, fixEndDerivative = True) + # get inner points + lavtx = [ [agx [0]], lavtx ] + lavtd1 = [ [agd1[0]], lavtd1 ] + lavtd2 = [ [agd2[0]], lavtd2 ] + lavtd3 = [ [agd3[0]], lavtd3 ] + for n1 in range(1, elementsCountAroundLeftAtriumFreeWall): + x, d1, _, d3 = interp.projectHermiteCurvesThroughWall(lavtx[1], lavtd1[1], lavtd2[1], n1, -laVenousFreeWallThickness) + # do same upwards to get proper value of d2 + nx = [ labx [1][n1], lavtx [1][n1] ] + nd1 = [ labd1[1][n1], lavtd1[1][n1] ] + nd2 = [ labd2[1][n1], lavtd2[1][n1] ] + _, d2, _, _ = interp.projectHermiteCurvesThroughWall(nx, nd2, [ [ -d for d in d1 ] for d1 in nd1 ], 1, -laVenousFreeWallThickness) + lavtx [0].append(x) + lavtd1[0].append(d1) + lavtd2[0].append(d2) + lavtd3[0].append(d3) + # fix d2 on inner base + labd2[0][n1] = interp.interpolateLagrangeHermiteDerivative(labx[0][n1], x, d2, 0.0) + lavtx [0].append(agx [-1]) + lavtd1[0].append(agd1[-1]) + lavtd2[0].append(agd2[-1]) + lavtd3[0].append(agd3[-1]) + # right atrium + ravtx = [ agx [-2] ] + ravtd1 = [ agd1[-2] ] + ravtd2 = [ agd2[-2] ] + ravtd3 = [ agd3[-2] ] + ravtProportions = [ [ ragProportions[-2], 0.0 ] ] + rabProportions = [ [ 1.0, 0.0 ] ] + ran1Ctp = elementsCountAroundRightAtriumPosteriorVenous + ran1Aorta = elementsCountAroundRightAtriumFreeWall - elementsCountAroundRightAtriumAorta + raApexx = raTrackSurface.nx[-1] + for n1 in range(1, elementsCountAroundRightAtriumFreeWall): + # find position on raTrackSurface corresponding to base outer node + # avoid point at end of raTrackSurface where derivative 1 is zero + onEnd = True + for c in range(3): + if math.fabs(rabx[1][n1][c] - raApexx[c]) > 0.0001: + onEnd = False + break + if onEnd: + startPosition = raTrackSurface.createPositionProportion(0.5, 0.9999) + else: + startProportion1 = (elementsCountAroundRightAtriumFreeWall - n1)/elementsCountAroundRightAtriumFreeWall + startPosition = raTrackSurface.findNearestPosition(rabx[1][n1], raTrackSurface.createPositionProportion(startProportion1, 0.5)) + rabProportions.append(raTrackSurface.getProportion(startPosition)) + onAorta = n1 == (elementsCountAroundRightAtriumFreeWall - 1) + direction = [ 0.0, 0.0, 1.0 ] if onAorta else vector.normalise(rabd2[1][n1]) + trackDistance1 = aoHeight1 if onAorta else aVestibuleOuterHeight + position = raTrackSurface.trackVector(startPosition, direction, trackDistance1) + ravtProportions.append(raTrackSurface.getProportion(position)) + x, d1, d2 = raTrackSurface.evaluateCoordinates(position, derivatives = True) + ax1, ax2, ax3 = calculate_surface_axes(d1, d2, direction) + ravtx .append(x) + ravtd1.append(vector.setMagnitude(ax2, -vector.magnitude(rabd1[1][n1]))) + ravtd2.append(vector.setMagnitude(ax1, trackDistance1)) + ravtd3.append(vector.setMagnitude(ax3, raVenousFreeWallThickness)) + # fix d2 on outer base + rabd2[1][n1] = vector.setMagnitude(rabd2[1][n1], trackDistance1) + # add end points and smooth d1 + ravtx .append(agx [1]) + ravtd1.append(agd1[1]) + ravtd2.append(agd2[1]) + ravtd3.append(agd3[1]) + ravtProportions.append([ ragProportions[1], 0.0 ]) + rabProportions.append([ 0.0, 0.0 ]) + # get inner points + ravtx = [ [agx [-1]], ravtx ] + ravtd1 = [ [agd1[-1]], ravtd1 ] + ravtd2 = [ [agd2[-1]], ravtd2 ] + ravtd3 = [ [agd3[-1]], ravtd3 ] + for n1 in range(1, elementsCountAroundRightAtriumFreeWall): + x, d1, _, d3 = interp.projectHermiteCurvesThroughWall(ravtx[1], ravtd1[1], ravtd2[1], n1, + -(raVenousFreeWallThickness if (n1 < elementsCountAroundRightAtriumPosteriorVenous) else raaWallThickness)) + # do same upwards to get proper value of d2 + nx = [ rabx [1][n1], ravtx [1][n1] ] + nd1 = [ rabd1[1][n1], ravtd1[1][n1] ] + nd2 = [ rabd2[1][n1], ravtd2[1][n1] ] + _, d2, _, _ = interp.projectHermiteCurvesThroughWall(nx, nd2, [ [ -d for d in d1 ] for d1 in nd1 ], 1, -raVenousFreeWallThickness) + ravtx [0].append(x) + ravtd1[0].append(d1) + ravtd2[0].append(d2) + ravtd3[0].append(d3) + # fix d2 on inner base + rabd2[0][n1] = interp.interpolateLagrangeHermiteDerivative(rabx[0][n1], x, d2, 0.0) + ravtx [0].append(agx [0]) + ravtd1[0].append(agd1[0]) + ravtd2[0].append(agd2[0]) + ravtd3[0].append(agd3[0]) + + # get points on left atrium over appendage, from aorta to laa end on vestibule top + agn1 = 2 if commonLeftRightPvOstium else 1 + asn1 = elementsCountAroundAtrialSeptum + 1 if commonLeftRightPvOstium else elementsCountAroundAtrialSeptum + startScaling = 2.0 if commonLeftRightPvOstium else 1.0 + laoax, laoad1, laoad2, laoad3, laoaProportions = laTrackSurface.createHermiteCurvePoints( + 1.0 - ragProportions[agn1], 0.0, + lavtProportions[lan1Mid][0], lavtProportions[lan1Mid][1], + elementsCount = elementsCountAroundLeftAtriumRPV + elementsCountOverSideLeftAtriumLPV, + derivativeStart = [ startScaling*d for d in agd1[agn1] ], + derivativeEnd = [ -1.0*d for d in lavtd2[1][lan1Mid] ]) + # get inner points + laoax = [ [ None ], laoax ] + laoad1 = [ [ None ], laoad1 ] + laoad2 = [ [ None ], laoad2 ] + laoad3 = [ [ None ], laoad3 ] + for n in range(1, len(laoax[1])): + x, d1, d2, d3 = interp.projectHermiteCurvesThroughWall(laoax[1], laoad1[1], laoad2[1], n, -laVenousFreeWallThickness) + laoax [0].append(x) + laoad1[0].append(d1) + laoad2[0].append(d2) + laoad3[0].append(d3) + laoad3[1][n] = d3 + # substitute known start and end coordinates + laoax [0][ 0] = asx [0][asn1] + laoad1[0][ 0] = asd1[0][asn1] + laoad2[0][ 0] = asd2[0][asn1] + laoad3[0][ 0] = asd3[0][asn1] + laoax [1][ 0] = agx [agn1] + laoad1[1][ 0] = agd1[agn1] + laoad2[1][ 0] = agd2[agn1] + laoad3[1][ 0] = agd3[agn1] + for n3 in range(2): + laoax [n3][-1] = lavtx [n3][lan1Mid] + laoad1[n3][-1] = lavtd1[n3][lan1Mid] + laoad2[n3][-1] = lavtd2[n3][lan1Mid] + laoad3[n3][-1] = lavtd3[n3][lan1Mid] + # smooth d2 to fit adjacent LPV derivative 2 + if commonLeftRightPvOstium: + n1Start = 1 + n1lpvStart = interp.getNearestPointIndex(lpvox[1], agx[agn1Mid]) - elementsCountOverLeftAtriumVenous//2 - 1 + else: + n1Start = elementsCountAroundLeftAtriumRPV + 1 + n1lpvStart = -2 - elementsCountOverLeftAtriumVenous//2 + for n1 in range(n1Start, len(laoax[1]) - 1): + n1lpv = n1lpvStart - (n1 - n1Start) + #print('n1',n1,'n1lpv',n1lpv) + #print('smooth laoa n1',n1,'lpv',n1lpv) + for n3 in range(2): + nx = [ laoax [n3][n1], lpvox [n3][n1lpv] ] + nd1 = [ laoad2[n3][n1], [ -d for d in lpvod2[n3][n1lpv] ] ] + laoad2[n3][n1] = interp.smoothCubicHermiteDerivativesLine(nx, nd1, fixAllDirections = True, fixEndDerivative = True)[0] + + if not commonLeftRightPvOstium: + # get points on row above left atrium venous anterior, from interatrial septum to nearest point on LPV ostium + # sample points up to venous midpoint, between RPV and laoa + agn1va = elementsCountOverLeftAtriumNonVenousAnterior + lavbx = [ agx [agn1va] ] + lavbd1 = [ agd1[agn1va] ] + lavbd2 = [ agd2[agn1va] ] + lavbd3 = [ agd3[agn1va] ] + lavbd2inner = [ None ] + lavbProportions = [ [ aVenousAnteriorOver, 0.0 ] ] + for n1 in range(1, elementsCountAroundLeftAtriumRPV + 1): + n1rpv = -elementsCountOverLeftAtriumVenous//2 - n1 + rpvoProportion1, rpvoProportion2 = laTrackSurface.getProportion(rpvoPositions[n1rpv]) + _x, _d2, _d1, _d3, _proportions = laTrackSurface.createHermiteCurvePoints( + laoaProportions[n1][0], laoaProportions[n1][1], + rpvoProportion1, rpvoProportion2, + elementsCount = 2, + derivativeStart = laoad2[1][n1], # GRC automatic value of equal magnitude to d1, looks ok + derivativeEnd = [ -d for d in rpvod2[1][n1rpv] ]) + lavbx .append(_x [1]) + lavbd1.append([ -d for d in _d1[1] ]) + lavbd2.append(_d2[1]) + lavbd3.append(_d3[1]) + lavbProportions.append(_proportions[1]) + # get precise inner d2 + _, _d2inner, _, _ = interp.projectHermiteCurvesThroughWall(_x, _d2, _d1, 1, -laVenousFreeWallThickness) + lavbd2inner.append(_d2inner) + # add nearest point on LPV ostium + n1lpv = -elementsCountOverLeftAtriumVenous//2 + lpvoProportion1, lpvoProportion2 = laTrackSurface.getProportion(lpvoPositions[n1lpv]) + lavbx .append(lpvox [1][n1lpv]) + lavbd1.append([ -d for d in lpvod2[1][n1lpv] ]) + lavbd2.append(lpvod1[1][n1lpv]) + lavbd3.append(lpvod3[1][n1lpv]) + lavbProportions.append([ lpvoProportion1, lpvoProportion2 ]) + # smooth d1: + lavbd1 = interp.smoothCubicHermiteDerivativesLine(lavbx, lavbd1, fixAllDirections = True, + fixStartDerivative = True, fixEndDerivative = True, magnitudeScalingMode = interp.DerivativeScalingMode.HARMONIC_MEAN) + # get inner points + lavbx = [ [ None ], lavbx ] + lavbd1 = [ [ None ], lavbd1 ] + lavbd2 = [ [ None ], lavbd2 ] + lavbd3 = [ [ None ], lavbd3 ] + for n1 in range(1, len(lavbx[1]) - 1): + x, d1, _, d3 = interp.projectHermiteCurvesThroughWall(lavbx[1], lavbd1[1], lavbd2[1], n1, -laVenousFreeWallThickness) + lavbx [0].append(x) + lavbd1[0].append(d1) + lavbd2[0].append(lavbd2inner[n1]) + lavbd3[0].append(d3) + lavbd3[1][n1] = d3 + lavbx [0].append(None) + lavbd1[0].append(None) + lavbd2[0].append(None) + lavbd3[0].append(None) + # transfer/substitute known start and end coordinates + asn1va = elementsCountAroundAtrialSeptum - 1 + elementsCountOverLeftAtriumNonVenousAnterior + lavbx [0][0] = asx [0][asn1va] + lavbd1[0][0] = asd1[0][asn1va] + lavbd2[0][0] = asd2[0][asn1va] + lavbd3[0][0] = asd3[0][asn1va] + lavbx [1][0] = agx [0][agn1va] + lavbd1[1][0] = agd1[0][agn1va] + lavbd2[1][0] = agd2[0][agn1va] + lavbd3[1][0] = agd3[0][agn1va] + for n3 in range(2): + lavbx [1][-1] = lpvox [n3][n1lpv] + lavbd1[1][-1] = lpvod1[n3][n1lpv] + lavbd2[1][-1] = lpvod2[n3][n1lpv] + lavbd3[1][-1] = lpvod3[n3][n1lpv] + + agn1vp = elementsCountOverLeftAtriumNonVenousAnterior + elementsCountOverLeftAtriumVenous + if commonLeftRightPvOstium: + # get points on row above left atrium venous posterior, from interatrial septum to laoa[-2] + # sample points up to venous midpoint, between LPV and vestibule top + lavqx = [ agx [agn1vp] ] + lavqd1 = [ agd1[agn1vp] ] + lavqd2 = [ agd2[agn1vp] ] + lavqd3 = [ agd3[agn1vp] ] + lavqd2inner = [ None ] + lavqProportions = [ [ laVenousLimitPosterior, 0.0 ] ] + n1lpvStart = interp.getNearestPointIndex(lpvox[1], agx[agn1Mid]) + elementsCountOverLeftAtriumVenous//2 - elementsCountAroundLpvOstium + for n1 in range(1, elementsCountAroundLeftAtriumRPV + elementsCountAroundLeftAtriumLPV): + n1lpv = n1lpvStart + n1 + n1cs = elementsCountAroundLeftAtriumFreeWall - n1 + lpvoProportion1, lpvoProportion2 = laTrackSurface.getProportion(lpvoPositions[n1lpv]) + _x, _d2, _d1, _d3, _proportions = laTrackSurface.createHermiteCurvePoints( + lpvoProportion1, lpvoProportion2, + lavtProportions[n1cs][0], lavtProportions[n1cs][1], + elementsCount = 2, + derivativeStart = lpvod2[1][n1lpv], derivativeEnd = [ -d for d in lavtd2[1][n1cs] ]) + lavqx .append(_x [1]) + lavqd1.append([ -d for d in _d1[1] ]) + lavqd2.append(_d2[1]) + lavqd3.append(_d3[1]) + lavqProportions.append(_proportions[1]) + # get precise inner d2 + _, _d2inner, _, _ = interp.projectHermiteCurvesThroughWall(_x, _d2, _d1, 1, -laVenousFreeWallThickness) + lavqd2inner.append(_d2inner) + lavqd2inner.append(laoad1[0][-2]) + # add 2nd last point on laoa + lavqx .append(laoax [1][-2]) + lavqd1.append([ -d for d in laoad2[1][-2] ]) + lavqd2.append(laoad1[1][-2]) + lavqd3.append(laoad3[1][-2]) + lavqProportions.append(laoaProportions[-2]) + # smooth d1: + lavqd1 = interp.smoothCubicHermiteDerivativesLine(lavqx, lavqd1, fixAllDirections = True, + fixStartDerivative = True, magnitudeScalingMode = interp.DerivativeScalingMode.HARMONIC_MEAN) + # get inner points + lavqx = [ [ None ], lavqx ] + lavqd1 = [ [ None ], lavqd1 ] + lavqd2 = [ [ None ], lavqd2 ] + lavqd3 = [ [ None ], lavqd3 ] + for n1 in range(1, len(lavqx[1])): + x, d1, _, d3 = interp.projectHermiteCurvesThroughWall(lavqx[1], lavqd1[1], lavqd2[1], n1, -laVenousFreeWallThickness) + lavqx [0].append(x) + lavqd1[0].append(d1) + lavqd2[0].append(lavqd2inner[n1]) + lavqd3[0].append(d3) + lavqd3[1][n1] = d3 + # transfer/substitute known start and end coordinates + asn1vp = -1 + lavqx [0][0] = asx [0][asn1vp] + lavqd1[0][0] = asd1[0][asn1vp] + lavqd2[0][0] = asd2[0][asn1vp] + lavqd3[0][0] = asd3[0][asn1vp] + lavqx [1][0] = asx [0][agn1vp] + lavqd1[1][0] = asd1[0][agn1vp] + lavqd2[1][0] = asd2[0][agn1vp] + lavqd3[1][0] = asd3[0][agn1vp] + for n3 in range(2): + laoad2[n3][-2] = [ -d for d in lavqd1[n3][-1] ] # use final d1 on laoa + lavqx [n3][-1] = laoax [n3][-2] + lavqd1[n3][-1] = laoad1[n3][-2] + lavqd2[n3][-1] = laoad2[n3][-2] + lavqd3[n3][-1] = laoad3[n3][-2] + else: + # get points on row above left atrium venous posterior, from interatrial septum to nearest point on LPV ostium + # sample points up to venous midpoint, between RPV and vestibule top + lavqx = [ agx [agn1vp] ] + lavqd1 = [ agd1[agn1vp] ] + lavqd2 = [ agd2[agn1vp] ] + lavqd3 = [ agd3[agn1vp] ] + lavqd2inner = [ None ] + lavqProportions = [ [ laVenousLimitPosterior, 0.0 ] ] + for n1 in range(1, elementsCountAroundLeftAtriumRPV + 1): + n1rpv = elementsCountOverLeftAtriumVenous//2 + n1 + n1cs = elementsCountAroundLeftAtriumFreeWall - n1 + rpvoProportion1, rpvoProportion2 = laTrackSurface.getProportion(rpvoPositions[n1rpv]) + _x, _d2, _d1, _d3, _proportions = laTrackSurface.createHermiteCurvePoints( + rpvoProportion1, rpvoProportion2, + lavtProportions[n1cs][0], lavtProportions[n1cs][1], + elementsCount = 2, + derivativeStart = rpvod2[1][n1rpv], derivativeEnd = [ -d for d in lavtd2[1][n1cs] ]) + lavqx .append(_x [1]) + lavqd1.append([ -d for d in _d1[1] ]) + lavqd2.append(_d2[1]) + lavqd3.append(_d3[1]) + lavqProportions.append(_proportions[1]) + # get precise inner d2 + _, _d2inner, _, _ = interp.projectHermiteCurvesThroughWall(_x, _d2, _d1, 1, -laVenousFreeWallThickness) + lavqd2inner.append(_d2inner) + # add nearest point on LPV ostium + n1lpv = elementsCountOverLeftAtriumVenous//2 + lpvoProportion1, lpvoProportion2 = laTrackSurface.getProportion(lpvoPositions[n1lpv]) + lavqx .append(lpvox [1][n1lpv]) + lavqd1.append([ -d for d in lpvod2[1][n1lpv] ]) + lavqd2.append(lpvod1[1][n1lpv]) + lavqd3.append(lpvod3[1][n1lpv]) + lavqProportions.append([ lpvoProportion1, lpvoProportion2 ]) + # smooth d1: + lavqd1 = interp.smoothCubicHermiteDerivativesLine(lavqx, lavqd1, fixAllDirections = True, + fixStartDerivative = True, fixEndDerivative = True, magnitudeScalingMode = interp.DerivativeScalingMode.HARMONIC_MEAN) + # get inner points + lavqx = [ [ None ], lavqx ] + lavqd1 = [ [ None ], lavqd1 ] + lavqd2 = [ [ None ], lavqd2 ] + lavqd3 = [ [ None ], lavqd3 ] + for n1 in range(1, len(lavqx[1]) - 1): + x, d1, _, d3 = interp.projectHermiteCurvesThroughWall(lavqx[1], lavqd1[1], lavqd2[1], n1, -laVenousFreeWallThickness) + lavqx [0].append(x) + lavqd1[0].append(d1) + lavqd2[0].append(lavqd2inner[n1]) + lavqd3[0].append(d3) + lavqd3[1][n1] = d3 + lavqx [0].append(None) + lavqd1[0].append(None) + lavqd2[0].append(None) + lavqd3[0].append(None) + # transfer/substitute known start and end coordinates + asn1vp = -1 + lavqx [0][0] = asx [0][asn1vp] + lavqd1[0][0] = asd1[0][asn1vp] + lavqd2[0][0] = asd2[0][asn1vp] + lavqd3[0][0] = asd3[0][asn1vp] + lavqx [1][0] = asx [0][agn1vp] + lavqd1[1][0] = asd1[0][agn1vp] + lavqd2[1][0] = asd2[0][agn1vp] + lavqd3[1][0] = asd3[0][agn1vp] + for n3 in range(2): + lavqx [n3][-1] = lpvox [n3][n1lpv] + lavqd1[n3][-1] = lpvod1[n3][n1lpv] + lavqd2[n3][-1] = lpvod2[n3][n1lpv] + lavqd3[n3][-1] = lpvod3[n3][n1lpv] + + if not commonLeftRightPvOstium: + # get left atrium venous mid line from 2nd last points on lavb to lavq + # find and pass through midpoint between left and right PVs + n1lpv = 0 + n1rpv = elementsCountOverLeftAtriumVenous + elementsCountAroundLeftAtriumRPV + rpvoProportion1, rpvoProportion2 = laTrackSurface.getProportion(rpvoPositions[n1rpv]) + lpvoProportion1, lpvoProportion2 = laTrackSurface.getProportion(lpvoPositions[n1lpv]) + mpx, mpd1, mpd2, _, mpProportions = laTrackSurface.createHermiteCurvePoints(rpvoProportion1, rpvoProportion2, lpvoProportion1, lpvoProportion2, + elementsCount = 2, derivativeStart = rpvod2[1][n1rpv], derivativeEnd = [ -d for d in lpvod2[1][n1lpv] ]) + # scale mid derivative 2 to be mean of d1 in LPV, RPV + d2mag = 0.5*vector.magnitude(lpvod1[1][n1lpv]) + 0.5*vector.magnitude(rpvod1[1][n1rpv]) + mpd2[1] = vector.setMagnitude(mpd2[1], d2mag) + lamlx, lamld2, lamld1, lamld3, lamlProportions = laTrackSurface.createHermiteCurvePoints( + lavbProportions[elementsCountAroundLeftAtriumRPV][0], lavbProportions[elementsCountAroundLeftAtriumRPV][1], + mpProportions[1][0], mpProportions[1][1], + elementsCount = elementsCountOverLeftAtriumVenous//2, + derivativeStart = [ (lavbd1[1][elementsCountAroundLeftAtriumRPV][c] + lavbd2[1][elementsCountAroundLeftAtriumRPV][c]) for c in range(3) ], + derivativeEnd = mpd2[1]) + _lamlx, _lamld2, _lamld1, _lamld3, _lamlProportions = laTrackSurface.createHermiteCurvePoints( + mpProportions[1][0], mpProportions[1][1], + lavqProportions[elementsCountAroundLeftAtriumRPV][0], lavqProportions[elementsCountAroundLeftAtriumRPV][1], + elementsCount = elementsCountOverLeftAtriumVenous//2, + derivativeStart = mpd2[1], + derivativeEnd = [ (-lavqd1[1][elementsCountAroundLeftAtriumRPV][c] + lavqd2[1][elementsCountAroundLeftAtriumRPV][c]) for c in range(3) ]) + lamlx += _lamlx [1:] + lamld1 += _lamld1[1:] + lamld2 += _lamld2 [1:] + lamld3 += _lamld3 [1:] + lamlProportions += _lamlProportions[1:] + # reverse d1 + lamld1 = [ [ -d for d in d1 ] for d1 in lamld1 ] + if elementsCountOverLeftAtriumVenous == 2: + # recalculate central d2 to match end derivatives + lamld2[1] = interp.smoothCubicHermiteDerivativesLine(lamlx, lamld2, fixAllDirections = True, + fixStartDerivative = True, fixEndDerivative = True, magnitudeScalingMode = interp.DerivativeScalingMode.HARMONIC_MEAN)[1] + # get inner points + lamlx = [ [ None ], lamlx ] + lamld1 = [ [ None ], lamld1 ] + lamld2 = [ [ None ], lamld2 ] + lamld3 = [ [ None ], lamld3 ] + lamlProportions += _lamlProportions[1:] + for n2 in range(1, elementsCountOverLeftAtriumVenous + 1): + x, d2, d1, d3 = interp.projectHermiteCurvesThroughWall(lamlx[1], lamld2[1], [ [ -d for d in d1 ] for d1 in lamld1[1] ], n2, -laVenousFreeWallThickness) + lamlx [0].append(x) + lamld1[0].append([ -d for d in d1 ]) + lamld2[0].append(d2) + lamld3[0].append(d3) + lamld3[1][n2] = d3 + # smooth d1 between RPV, LPV + for n2 in range(1, elementsCountOverLeftAtriumVenous): + n1lpv = n2 - elementsCountOverLeftAtriumVenous//2 - 1 + n1rpv = n2 - elementsCountOverLeftAtriumVenous - elementsCountAroundLeftAtriumRPV - 1 + for n3 in range(2): + nx = [ rpvox [n3][n1rpv], lamlx [n3][n2], lpvox [n3][n1lpv] ] + nd1 = [ rpvod1[n3][n1rpv], lamld1[n3][n2], [ -d for d in lpvod1[n3][n1lpv] ] ] + d1 = interp.smoothCubicHermiteDerivativesLine(nx, nd1, fixAllDirections = True, + fixStartDerivative = True, fixEndDerivative = True, magnitudeScalingMode = interp.DerivativeScalingMode.HARMONIC_MEAN)[1] + lamld1[n3][n2] = d1 + # substitute known start and end coordinates + for n3 in range(2): + lamlx [n3][ 0] = lavbx [n3][elementsCountAroundLeftAtriumRPV] + lamld1[n3][ 0] = lavbd1[n3][elementsCountAroundLeftAtriumRPV] + lamld2[n3][ 0] = lavbd2[n3][elementsCountAroundLeftAtriumRPV] + lamld3[n3][ 0] = lavbd3[n3][elementsCountAroundLeftAtriumRPV] + lamlx [n3][-1] = lavqx [n3][elementsCountAroundLeftAtriumRPV] + lamld1[n3][-1] = lavqd1[n3][elementsCountAroundLeftAtriumRPV] + lamld2[n3][-1] = lavqd2[n3][elementsCountAroundLeftAtriumRPV] + lamld3[n3][-1] = lavqd3[n3][elementsCountAroundLeftAtriumRPV] + + # get points on right atrium along crista terminalis from aorta to posterior venous limit + xi = (1.0 - aVenousMidpointOver - ravtProportions[ran1Aorta][0])/(ravtProportions[ran1Ctp][0] - ravtProportions[ran1Aorta][0]) + rctmpProportion1 = 1.0 - aVenousMidpointOver + rctmpProportion2 = raVenousRight # GRC was (failing) (1.0 - xi)*ravtProportions[ran1Aorta][1] + xi*ravtProportions[ran1Ctp][1] + elementsCountOverCristaTerminalisAnterior = elementsCountOverRightAtriumVenous//2 + 1 + elementsCountOverCristaTerminalisPosterior = elementsCountOverRightAtriumVenous//2 + # trick to get lower derivative at midpoint: sample one element higher + _, _d2 = raTrackSurface.createHermiteCurvePoints( + rctmpProportion1, rctmpProportion2, ravtProportions[ran1Ctp][0], ravtProportions[ran1Ctp][1], + elementsCount = elementsCountOverCristaTerminalisPosterior + 1, + derivativeStart = None, + derivativeEnd = [ -d for d in ravtd2[1][ran1Ctp] ])[0:2] + useDerivativeStart = _d2[0] + _ractx, _ractd2, _ractd1, _ractd3, _ractProportions = raTrackSurface.createHermiteCurvePoints( + rctmpProportion1, rctmpProportion2, ravtProportions[ran1Ctp][0], ravtProportions[ran1Ctp][1], + elementsCount = elementsCountOverCristaTerminalisPosterior, + derivativeStart = useDerivativeStart, + derivativeEnd = [ -d for d in ravtd2[1][ran1Ctp] ]) + ractx, ractd2, ractd1, ractd3, ractProportions = raTrackSurface.createHermiteCurvePoints( + ragProportions[1], 0.0, + rctmpProportion1, rctmpProportion2, + elementsCount = elementsCountOverCristaTerminalisAnterior, + derivativeStart = [ -1.5*d for d in agd1[1] ], # GRC fudge factor + derivativeEnd = _ractd2[0]) + ractx += _ractx [1:] + ractd1 += _ractd1[1:] + ractd2 += _ractd2[1:] + ractd3 += _ractd3[1:] + ractProportions += _ractProportions[1:] + # get right atrium posterior venous width around vestibule top to crista terminalis, to scale set ct derivative 1 below + raVenousWidth = sum(interp.getCubicHermiteArcLength(ravtx[1][e], ravtd1[1][e], ravtx[1][e + 1], ravtd1[1][e + 1]) for e in range(elementsCountAroundRightAtriumPosteriorVenous)) + d1mag = -0.2*raVenousWidth # GRC fudge factor + ractx = [ [], ractx ] + ractd1 = [ [], [ vector.setMagnitude(d1, d1mag) for d1 in ractd1 ] ] + ractd2 = [ [], ractd2 ] + ractd3 = [ [], ractd3 ] + for n in range(len(ractx[1])): + x, d2, d1, d3 = interp.projectHermiteCurvesThroughWall(ractx[1], ractd2[1], [ [ -d for d in d1 ] for d1 in ractd1[1] ], n, -cristaTerminalisThickness) + ractx [0].append(x) + ractd1[0].append([ -d for d in d1 ]) + ractd2[0].append(d2) + ractd3[0].append(d3) + ractd3[1][n] = d3 + for n3 in range(2): + # overwrite venous right x, d1 on vestibule top + ravtx [n3][ran1Ctp] = ractx [n3][-1] + d1mag = min(vector.magnitude(ravtd1[n3][ran1Ctp]), 1.0*vector.magnitude(ractd1[n3][-1])) # GRC fudge factor + ravtd1[n3][ran1Ctp] = vector.setMagnitude(ravtd1[n3][ran1Ctp], d1mag) + # substitute known start and end coordinates + ractx [0][ 0] = asx [1][elementsCountAroundAtrialSeptum] + ractd1[0][ 0] = asd1[1][elementsCountAroundAtrialSeptum] + ractd2[0][ 0] = asd2[1][elementsCountAroundAtrialSeptum] + ractd3[0][ 0] = asd3[1][elementsCountAroundAtrialSeptum] + ractx [1][ 0] = agx [1] + ractd1[1][ 0] = agd1[1] + ractd2[1][ 0] = agd2[1] + ractd3[1][ 0] = agd3[1] + for n3 in range(2): + ractx [n3][-1] = ravtx [n3][ran1Ctp] + ractd1[n3][-1] = ravtd1[n3][ran1Ctp] + ractd2[n3][-1] = ravtd2[n3][ran1Ctp] + ractd3[n3][-1] = ravtd3[n3][ran1Ctp] + + # get points on right atrium ridge midway between inferior and superior vena cavae from crista terminalis to interatrial groove + # minimum of 2 points over top of venous component + elementsCountOverSideRightAtriumVC = max(elementsCountAroundRightAtriumPosteriorVenous, 2) + ravmx, ravmd1, ravmd2, ravmd3 = raTrackSurface.createHermiteCurvePoints( + ractProportions[elementsCountOverCristaTerminalisAnterior][0], ractProportions[elementsCountOverCristaTerminalisAnterior][1], + 1.0 - aVenousMidpointOver, 0.0, + elementsCount = elementsCountOverSideRightAtriumVC, + derivativeStart = [ 2.0*d for d in ractd1[1][elementsCountOverCristaTerminalisAnterior] ], # GRC fudge factor: d1 is artificially reduced on crista terminalis + derivativeEnd = agd1[agn1Mid])[0:4] + # get inner points + ravmx = [ [], ravmx ] + ravmd1 = [ [], ravmd1 ] + ravmd2 = [ [], ravmd2 ] + ravmd3 = [ [], ravmd3 ] + # blend d2 between ends: + magc = vector.magnitude(ractd2[1][elementsCountOverCristaTerminalisAnterior]) + maga = vector.magnitude(agd2[agn1Mid]) + for n in range(1, elementsCountOverSideRightAtriumVC): + xi = n/elementsCountOverSideRightAtriumVC + ravmd2[1][n] = vector.setMagnitude(ravmd2[1][n], (1.0 - xi)*magc + xi*maga) + for n in range(len(ravmx[1])): + x, d1, d2, d3 = interp.projectHermiteCurvesThroughWall(ravmx[1], ravmd1[1], ravmd2[1], n, -raVenousFreeWallThickness) + ravmx [0].append(x) + ravmd1[0].append(d1) + ravmd2[0].append(d2) + ravmd3[0].append(d3) + ravmd3[1][n] = d3 + # substitute known end coordinates + for n3 in range(2): + # copy d1 back to crista terminalis + ractd1[n3][elementsCountOverCristaTerminalisAnterior] = ravmd1[n3][0] + ravmd2[n3][0] = ractd2[n3][elementsCountOverCristaTerminalisAnterior] + asn1Mid = elementsCountAroundAtrialSeptum + agn1Mid - 1 + ravmx [0][-1] = asx [1][asn1Mid] + ravmd1[0][-1] = asd1[1][asn1Mid] + ravmd2[0][-1] = asd2[1][asn1Mid] + ravmd3[0][-1] = asd3[1][asn1Mid] + ravmx [1][-1] = agx [agn1Mid] + ravmd1[1][-1] = agd1[agn1Mid] + ravmd2[1][-1] = agd2[agn1Mid] + ravmd3[1][-1] = agd3[agn1Mid] + + # get points over right atrial appendage from anterior end of crista terminalis to end of pouch on vestibule top + assert elementsCountAroundRightAtrialAppendagePlainBase == 2 + elementsCountOverSideRightAtriumPouch = elementsCountOverAtria//2 + elementsCountOverCristaTerminalisAnterior - 3 + ran1raap = ran1Ctp + elementsCountAroundRightAtrialAppendagePlainBase + nc = 2 + raapx, raapd1, raapd2, raapd3, raapProportions = raTrackSurface.createHermiteCurvePoints( + ractProportions[nc][0], ractProportions[nc][1], + ravtProportions[ran1raap][0], ravtProportions[ran1raap][1], + elementsCount = elementsCountOverSideRightAtriumPouch, + derivativeStart = [ (ractd2[1][nc][c] - ractd1[1][nc][c]) for c in range(3) ], + derivativeEnd = [ -d for d in ravtd2[1][ran1raap] ]) + # get inner points + raapx = [ [ None ], raapx ] + raapd1 = [ [ None ], raapd1 ] + raapd2 = [ [ None ], raapd2 ] + raapd3 = [ [ None ], raapd3 ] + for n in range(1, len(raapx[1])): + x, d1, d2, d3 = interp.projectHermiteCurvesThroughWall(raapx[1], raapd1[1], raapd2[1], n, -raaWallThickness) + raapx [0].append(x) + raapd1[0].append(d1) + raapd2[0].append(d2) + raapd3[0].append(d3) + raapd3[1][n] = d3 + # substitute known end coordinates + for n3 in range(2): + raapx [n3][ 0] = ractx [n3][nc] + raapd1[n3][ 0] = ractd1[n3][nc] + raapd2[n3][ 0] = ractd2[n3][nc] + raapd3[n3][ 0] = ractd3[n3][nc] + raapx [n3][-1] = ravtx [n3][ran1raap] + raapd1[n3][-1] = ravtd1[n3][ran1raap] + raapd2[n3][-1] = ravtd2[n3][ran1raap] + raapd3[n3][-1] = ravtd3[n3][ran1raap] + # get second row between raap and crista terminalis: raaq + ran1raaq = ran1Ctp + elementsCountAroundRightAtrialAppendagePlainBase - 1 + nc = 2 + raaqx, raaqd1, raaqd2, raaqd3, raaqProportions = raTrackSurface.createHermiteCurvePoints( + ractProportions[nc][0], ractProportions[nc][1], + ravtProportions[ran1raaq][0], ravtProportions[ran1raaq][1], + elementsCount = elementsCountOverAtria//2 + elementsCountOverCristaTerminalisAnterior - 3, + derivativeStart = [ (ractd2[1][nc][c] - 0.5*ractd1[1][nc][c]) for c in range(3) ], + derivativeEnd = [ -d for d in ravtd2[1][ran1raaq] ]) # get inner points - for n2 in range(1, elementsCountUpAtria + 1): - for n1 in range(elementsCountAroundAtria): - ox = laOuterx[n2][n1] - if ox is None: + raaqx = [ [ None ], raaqx ] + raaqd1 = [ [ None ], raaqd1 ] + raaqd2 = [ [ None ], raaqd2 ] + raaqd3 = [ [ None ], raaqd3 ] + for n in range(1, len(raaqx[1])): + x, d1, d2, d3 = interp.projectHermiteCurvesThroughWall(raaqx[1], raaqd1[1], raaqd2[1], n, -raaWallThickness) + raaqx [0].append(x) + raaqd1[0].append(d1) + raaqd2[0].append(d2) + raaqd3[0].append(d3) + raaqd3[1][n] = d3 + # substitute known end coordinates + for n3 in range(2): + raaqx [n3][ 0] = ractx [n3][nc] + raaqd1[n3][ 0] = ractd1[n3][nc] + raaqd2[n3][ 0] = ractd2[n3][nc] + raaqd3[n3][ 0] = ractd3[n3][nc] + raaqx [n3][-1] = ravtx [n3][ran1raaq] + raaqd1[n3][-1] = ravtd1[n3][ran1raaq] + raaqd2[n3][-1] = ravtd2[n3][ran1raaq] + raaqd3[n3][-1] = ravtd3[n3][ran1raaq] + # smooth d2 between raap and raaq + for n1 in range(2, len(raapx[1]) - 1): + nx = [ raaqx [1][n1], raapx [1][n1] ] + nd1 = [ raaqd2[1][n1], raapd2[1][n1] ] + nd2 = [ [ -d for d in d1 ] for d1 in [ raaqd1[1][n1], raapd1[1][n1] ] ] + raaqd2[1][n1], raapd2[1][n1] = nd1 = interp.smoothCubicHermiteDerivativesLine(nx, nd1, fixAllDirections = True) + # get inner derivatives + _, raaqd2[0][n1], _, _ = interp.projectHermiteCurvesThroughWall(nx, nd1, nd2, 0, -raaWallThickness) + _, raapd2[0][n1], _, _ = interp.projectHermiteCurvesThroughWall(nx, nd1, nd2, 1, -raaWallThickness) + # make point in centre of triangle at top of raaq, for 3 quad element junction + raaqProportions[1] = [ (ractProportions[nc + 1][i] + raapProportions[1][i] + raaqProportions[2][i])/3.0 for i in range(2) ] + position = raTrackSurface.createPositionProportion(raaqProportions[1][0], raaqProportions[1][1]) + raaqx[1][1], d1, d2 = raTrackSurface.evaluateCoordinates(position, derivatives = True) + # calculate derivative 1 there to fit nearest point on raaq + raaqd1[1][1] = interp.interpolateLagrangeHermiteDerivative(raaqx[1][1], raaqx[1][2], raaqd1[1][2], 0.0) + raaqd1[1][1] = vector.setMagnitude(calculate_surface_axes(d1, d2, raaqd1[1][1])[0], vector.magnitude(raaqd1[1][1])) + raaqd1[1][1] = interp.smoothCubicHermiteDerivativesLine(raaqx[1][1:3], raaqd1[1][1:3], fixAllDirections = True, fixEndDerivative = True)[0] + # calculate derivative 2 there and on nearest point on raap + # raapd2[1][1] magnitude needs to be set to fit distance between nodes: + d2mag = math.sqrt(sum((raapx[1][1][c] - raaqx[1][1][c])*(raapx[1][1][c] - raaqx[1][1][c]) for c in range(3))) + raapd2[1][1] = vector.setMagnitude(raapd2[1][1], d2mag) # must reduce this otherwise smooth will converge wrongly + raaqd2[1][1] = interp.interpolateLagrangeHermiteDerivative(raaqx[1][1], raapx[1][1], raapd2[1][1], 0.0) + raaqd2[1][1] = vector.setMagnitude(calculate_surface_axes(d1, d2, raaqd2[1][1])[0], vector.magnitude(raaqd2[1][1])) + raaqd2[1][1], raapd2[1][1] = interp.smoothCubicHermiteDerivativesLine([ raaqx[1][1], raapx[1][1] ], [ raaqd2[1][1], raapd2[1][1] ], fixAllDirections = True) + # get inner coordinates and derivatives + raaqx[0][1], raaqd1[0][1], _, raaqd3[0][1] = interp.projectHermiteCurvesThroughWall(raaqx[1][1:3], raaqd1[1][1:3], raaqd2[1][1:3], 0, -raaWallThickness) + raaqd3[1][1] = raaqd3[0][1] + nx = [ raaqx [1][1], raapx [1][1] ] + nd1 = [ raaqd2[1][1], raapd2[1][1] ] + nd2 = [ [ -d for d in d1 ] for d1 in [ raaqd1[1][1], raapd1[1][1] ] ] + _, raaqd2[0][1], _, _ = interp.projectHermiteCurvesThroughWall(nx, nd1, nd2, 0, -raaWallThickness) + _, raapd2[0][1], _, _ = interp.projectHermiteCurvesThroughWall(nx, nd1, nd2, 1, -raaWallThickness) + + # create la base nodes, adding to lFibrousRingGroup + labNodeId = [ [], [] ] + lFibrousRingNodesetGroup = lFibrousRingGroup.getNodesetGroup(nodes) + # create ra base nodes, adding to rFibrousRingGroup + rabNodeId = [ [], [] ] + nodesetGroup = rFibrousRingGroup.getNodesetGroup(nodes) + for n3 in range(2): + for n1 in range(len(labx[n3])): + if not labx[n3][n1]: continue - od1 = laOuterd1[n2][n1] - od2 = laOuterd2[n2][n1] - unitRadial = vector.normalise(vector.crossproduct3(od1, od2)) - id3 = od3 = [ aFreeWallThickness*unitRadial[c] for c in range(3) ] - laInnerd3[n2][n1] = laOuterd3[n2][n1] = od3 - ix = laInnerx[n2][n1] - if ix is not None: + node = nodes.createNode(nodeIdentifier, nodetemplate) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, labx [n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, labd1[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, labd2[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, labd3[n3][n1]) + lFibrousRingNodesetGroup.addNode(node) + labNodeId[n3].append(nodeIdentifier) + nodeIdentifier += 1 + + for n1 in range(len(rabx[n3])): + if not rabx[n3][n1]: continue - ix = [ (ox[c] - od3[c]) for c in range(3) ] - - # calculate inner d1 from curvature around - curvature = 0.0 - count = 0 - if (n1 < elementsCountAroundAtrialFreeWall) and (laOuterx[n2][n1 + 1] is not None): - curvature -= interp.getCubicHermiteCurvature(laOuterx[n2][n1], laOuterd1[n2][n1], laOuterx[n2][n1 + 1], laOuterd1[n2][n1 + 1], unitRadial, 0.0) - count += 1 - if (n1 > 0) and (laOuterx[n2][n1 - 1] is not None): - curvature -= interp.getCubicHermiteCurvature(laOuterx[n2][n1 - 1], laOuterd1[n2][n1 - 1], laOuterx[n2][n1], laOuterd1[n2][n1], unitRadial, 1.0) - count += 1 - curvature /= count - factor = 1.0 - curvature*aFreeWallThickness - id1 = [ factor*c for c in od1 ] - - # calculate inner d2 from curvature up - curvature = 0.0 - count = 0 - if (n2 < elementsCountUpAtria) and (laOuterx[n2 + 1][n1] is not None): - curvature -= interp.getCubicHermiteCurvature(laOuterx[n2][n1], laOuterd2[n2][n1], laOuterx[n2 + 1][n1], laOuterd2[n2 + 1][n1], unitRadial, 0.0) - count += 1 - if n2 > 0: - curvature -= interp.getCubicHermiteCurvature(laOuterx[n2 - 1][n1], laOuterd2[n2 - 1][n1], laOuterx[n2][n1], laOuterd2[n2][n1], unitRadial, 1.0) - count += 1 - curvature /= count - factor = 1.0 - curvature*aFreeWallThickness - id2 = [ factor*c for c in od2 ] - - laInnerx[n2][n1] = ix - laInnerd1[n2][n1] = id1 - laInnerd2[n2][n1] = id2 - laInnerd3[n2][n1] = id3 - - # fix inner base derivative 2 to fit incline - for n1 in range(1, elementsCountAroundAtrialFreeWall + 1): - d2 = interp.interpolateHermiteLagrangeDerivative(laInnerx[1][n1], [ -d for d in laInnerd2[1][n1] ], laInnerx[0][n1], 1.0) - laInnerd2[0][n1] = [ -d for d in d2 ] - # special fix inner base derivative 2 at cfb = slope of cfbLeft inner derivative 2 - laInnerd2[0][0] = laInnerd2[0][1] - - # calculate fossa positions early to use in smoothing septum - # GRC the following 4 variables could be made into options - fossaCentreY = laSeptumBaseCentrex[1] - fossaCentreZ = 0.5*aOuterSeptumHeight - # fossa width is based on distance from cfb to fossa centre - fossaMagY = (laBaseOuterx[0][1] - fossaCentreY)*0.36 - fossaMagZ = 0.2*aOuterSeptumHeight - - ux = [ laSeptumBaseCentrex[0], fossaCentreY, fossaCentreZ ] - ud1 = [ 0.0, fossaMagY, 0.0 ] - ud2 = [ 0.0, 0.0, fossaMagZ] - gx = laInnerx [elementsCountUpAtria][1] - gd1 = laInnerd1[elementsCountUpAtria][1] - gd2 = laInnerd2[elementsCountUpAtria][1] - gd3 = laInnerd3[elementsCountUpAtria][1] - fradians = math.atan2(gx[2] - ux[2], gx[1] - ux[1]) - fd1 = [ ( ud1[c]*math.cos(fradians) + ud2[c]*math.sin(fradians)) for c in range(3) ] - fd2 = [ (-ud1[c]*math.sin(fradians) + ud2[c]*math.cos(fradians)) for c in range(3) ] - fx = [ (ux[c] + fd1[c]) for c in range(3) ] - tx, td1, te, txi = interp.sampleCubicHermiteCurves([ fx, gx ], [ fd1, gd1 ], elementsCountOut = 2, - addLengthStart = 0.5*vector.magnitude(fd1), lengthFractionStart = 0.5, - addLengthEnd = 0.5*vector.magnitude(gd1), lengthFractionEnd = 0.5, arcLengthDerivatives = True)[0:4] - td2 = interp.interpolateSampleLinear([ fd2, gd2 ], te, txi) - mx = tx [1] - md1 = td1[1] - md2 = td2[1] + if n3 == 1: + # find common nodes on left atrium base + nodeId = None + if n1 == 0: # crux + nodeId = labNodeId[1][elementsCountAroundLeftAtriumFreeWall] + elif n1 == elementsCountAroundRightAtriumFreeWall: # cfb + nodeId = labNodeId[1][0] + if nodeId: + rabNodeId[n3].append(nodeId) + nodesetGroup.addNode(nodes.findNodeByIdentifier(nodeId)) + continue + node = nodes.createNode(nodeIdentifier, nodetemplate) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, rabx [n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, rabd1[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, rabd2[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, rabd3[n3][n1]) + nodesetGroup.addNode(node) + rabNodeId[n3].append(nodeIdentifier) + nodeIdentifier += 1 - if False: + # create interatrial groove nodes: + # start and end with common nodes at cfb and crux + agNodeId = [ labNodeId[1][0] ] # cfb + for n1 in range(1, len(agx) - 1): node = nodes.createNode(nodeIdentifier, nodetemplate) cache.setNode(node) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, fx ) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, fd1) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, fd2) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, agx [n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, agd1[n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, agd2[n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, agd3[n1]) + agNodeId.append(nodeIdentifier) nodeIdentifier += 1 + agNodeId.append(rabNodeId[1][0]) # crux + # create septum nodes, along vestibule top and over arch + asNodeId = [ [], [] ] + for n3 in range(2): + for ns in range(len(asx[n3])): + node = nodes.createNode(nodeIdentifier, nodetemplate) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, asx [n3][ns]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, asd1[n3][ns]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, asd2[n3][ns]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, asd3[n3][ns]) + asNodeId[n3].append(nodeIdentifier) + nodeIdentifier += 1 + + # create fossa ovalis nodes + foCentreNodeId = [] + foNodeId = [ [], [] ] + for n3 in range(2): node = nodes.createNode(nodeIdentifier, nodetemplate) cache.setNode(node) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, gx ) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, gd1) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, gd2) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, gd3) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, foCentrex [n3]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, foCentred1[n3]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, foCentred2[n3]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, foCentred3[n3]) + foCentreNodeId.append(nodeIdentifier) nodeIdentifier += 1 + for nf in range(elementsCountAroundFossa): + node = nodes.createNode(nodeIdentifier, nodetemplate) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, fox [n3][nf]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, fod1[n3][nf]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, fod2[n3][nf]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, fod3[n3][nf]) + foNodeId[n3].append(nodeIdentifier) + nodeIdentifier += 1 - # fix inner nodes near septum to thicken wall and transition to septum - ax = laInnerx [0][0] - ad1 = laInnerd1[0][0] - ad2 = laInnerd2[0][0] - # get start distance to account for aBaseSlopeRadians - scale2 = aBaseSlopeHeight/ad2[2] - addLengthStart = vector.magnitude([ ad2[0]*scale2, ad2[1]*scale2, aBaseSlopeHeight ]) - px = laInnerx [0][elementsCountAroundAtrialFreeWall] - pd1 = [ -d for d in laInnerd1[0][elementsCountAroundAtrialFreeWall] ] - pd2 = [ -d for d in laInnerd2[0][elementsCountAroundAtrialFreeWall] ] - # make steeper and with low x component - ad2 = [ 0.5*ad2[0], ad2[1], 1.5*ad2[2] ] - pd2 = [ 0.5*pd2[0], pd2[1], 1.5*pd2[2] ] - # get start distance to account for aBaseSlopeRadians - scale2 = -aBaseSlopeHeight/pd2[2] - addLengthEnd = vector.magnitude([ pd2[0]*scale2, pd2[1]*scale2, aBaseSlopeHeight ]) - ix, id2, ie, ixi = interp.sampleCubicHermiteCurves([ ax , mx , px ], [ ad2, md2, pd2 ], 2*elementsCountUpAtria, - addLengthStart, addLengthEnd, elementLengthStartEndRatio = aElementSizeRatioAnteriorPosterior, arcLengthDerivatives = True)[0:4] - id1 = interp.interpolateSampleLinear([ ad1, md1, pd1 ], ie, ixi) - for noa in range(elementsCountUpAtria*2 + 1): - nop = elementsCountUpAtria*2 - noa - if noa <= elementsCountUpAtria: - laInnerx[noa][0] = ix[noa] - laInnerd1[noa][0] = id1[noa] - laInnerd2[noa][0] = id2[noa] - else: - laInnerx[nop][elementsCountAroundAtrialFreeWall] = ix[noa] - laInnerd1[nop][elementsCountAroundAtrialFreeWall] = [ -d for d in id1[noa] ] - laInnerd2[nop][elementsCountAroundAtrialFreeWall] = [ -d for d in id2[noa] ] - - # fix derivative 3 through wall - n2 = 0 - for n1 in range(elementsCountAroundAtrialFreeWall + 1, elementsCountAroundAtria): - laInnerd3[n2][n1] = [ -2.0*laBaseInnerx[n1][0], 0.0, 0.0 ] - for n2 in range(elementsCountUpAtria + 1): - n1Limit = n1MidFreeWall if (n2 == elementsCountUpAtria) else (elementsCountAroundAtrialFreeWall + 1) - for n1 in range(n1Limit): - if laOuterx[n2][n1] is None: + # create left atrium vestibule top nodes + # start and end with common nodes on interatrial groove or septum arch + lavtNodeId = [ [ asNodeId[0][elementsCountAroundAtrialSeptum] ], [ agNodeId[1] ] ] + for n3 in range(2): + for n1 in range(1, len(lavtx[n3]) - 1): + if (elementsCountAroundLeftAtriumAorta < n1 < lan1Mid) or ((not commonLeftRightPvOstium) and (n1 == elementsCountAroundLeftAtriumAorta)): + # left atrial appendage + lavtNodeId[n3].append(None) continue - laInnerd3[n2][n1] = laOuterd3[n2][n1] = [ (laOuterx[n2][n1][c] - laInnerx[n2][n1][c]) for c in range(3) ] - # fix cfb-interatrial groove-crux centre derivative 3: - for n2 in range(elementsCountUpAtria + 1): - laOuterd3[n2][0] = [ 0.0, laOuterx[n2][0][1] - laInnerx[n2][0][1], laOuterx[n2][0][2] - laInnerx[n2][0][2] ] - for n2 in range(elementsCountUpAtria): - laOuterd3[n2][elementsCountAroundAtrialFreeWall] = [ 0.0, - laOuterx[n2][elementsCountAroundAtrialFreeWall][1] - laInnerx[n2][elementsCountAroundAtrialFreeWall][1], - laOuterx[n2][elementsCountAroundAtrialFreeWall][2] - laInnerx[n2][elementsCountAroundAtrialFreeWall][2] ] - - # fossa ovalis points at centre and around - elementsCountFossaBaseCentre = max(elementsCountAroundAtrialSeptum - 2, 1) - elementsCountFossaBase = 2 + elementsCountFossaBaseCentre - elementsCountAroundFossa = elementsCountFossaBaseCentre + 2*(elementsCountUpAtria - 1) - fossaPerimeterLength = getApproximateEllipsePerimeter(fossaMagY, fossaMagZ) - # allow more space for fossa base elements - elementSizeAroundFossa = fossaPerimeterLength/(elementsCountAroundFossa + 1) - elementSizeAroundFossaBase = (elementSizeAroundFossa*elementsCountFossaBase)/(elementsCountFossaBase - 1) - elementSizeAroundFossaTransition = 0.5*(elementSizeAroundFossa + elementSizeAroundFossaBase) - - radiansAround = updateEllipseAngleByArcLength(fossaMagY, fossaMagZ, fradians - math.pi, -0.5*elementsCountFossaBaseCentre*elementSizeAroundFossaBase) - fossaRadiansAround = [] - fossaDerivativesAround = [] - for nf in range(elementsCountAroundFossa): - fossaRadiansAround.append(radiansAround) - fossaDerivativesAround.append(elementSizeAroundFossaBase if (nf <= elementsCountFossaBaseCentre) else elementSizeAroundFossa) - radiansAround = updateEllipseAngleByArcLength(fossaMagY, fossaMagZ, radiansAround, \ - elementSizeAroundFossaBase if (nf < elementsCountFossaBaseCentre) \ - else (elementSizeAroundFossa if (nf > (elementsCountFossaBaseCentre)) - else elementSizeAroundFossaTransition)) - fossaCentrex = [] - fossaCentred1 = [] - fossaCentred2 = [] - fossaCentred3 = [] - fossax = [ [], [] ] - fossad1 = [ [], [] ] - fossad2 = [ [], [] ] - fossad3 = [ [], [] ] - septumd3 = [ aSeptumThickness, 0.0, 0.0 ] + node = nodes.createNode(nodeIdentifier, nodetemplate) + lavtNodeId[n3].append(nodeIdentifier) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, lavtx [n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, lavtd1[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, lavtd2[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, lavtd3[n3][n1]) + nodeIdentifier += 1 + lavtNodeId[0].append(asNodeId[0][0]) + lavtNodeId[1].append(agNodeId[-2]) + + # create right atrium vestibule top nodes + ravtNodeId = [ [ asNodeId[1][0] ], [ agNodeId[-2] ] ] for n3 in range(2): - fossaCentrex.append([ aSeptumThickness*(-0.5 if (n3 == 0) else 0.5), fossaCentreY, fossaCentreZ ]) - fossaCentred1.append([ 0.0, fossaMagY, 0.0 ]) - fossaCentred2.append([ 0.0, 0.0, fossaMagZ ]) - fossaCentred3.append(septumd3) - for nf in range(elementsCountAroundFossa): - radiansAround = fossaRadiansAround[nf] - cosRadiansAround = math.cos(radiansAround) - sinRadiansAround = math.sin(radiansAround) - fossax[n3].append([ fossaCentrex[n3][0], fossaCentreY + fossaMagY*cosRadiansAround, fossaCentreZ + fossaMagZ*sinRadiansAround ]) - fossad1[n3].append(vector.setMagnitude([ 0.0, -fossaMagY*sinRadiansAround, fossaMagZ*cosRadiansAround ], fossaDerivativesAround[nf])) - fossad2[n3].append([ 0.0, -fossaMagY*cosRadiansAround, -fossaMagZ*sinRadiansAround ]) - fossad3[n3].append(septumd3) - - # calculate base septum derivative 2 to smoothly connect to adjacent fossa nodes - if elementsCountAroundAtrialSeptum == 2: - laInnerd2[0][elementsCountAroundAtrialFreeWall + 1] = [ 0.0, 0.0, 2.0*(fossaCentreZ - fossaMagZ + aBaseSlopeHeight) - fossaMagZ ] - else: - for ns in range(1, elementsCountAroundAtrialSeptum): - x1 = laInnerx[0][elementsCountAroundAtrialFreeWall + ns] - x2 = fossax[0][ns - 1] - d2 = fossad2[0][ns - 1] - if ns == 1: - d2 = [ (d2[c] - fossad1[0][ns - 1][c]) for c in range(3) ] - elif ns == (elementsCountAroundAtrialSeptum - 1): - d2 = [ (d2[c] + fossad1[0][ns - 1][c]) for c in range(3) ] - laInnerd2[0][elementsCountAroundAtrialFreeWall + ns] = interp.interpolateLagrangeHermiteDerivative(x1, x2, d2, 0.0) - - # Create nodes around atria - pvEdgeDerivativeFactor1 = 0.50 # GRC fudge factor: factor reducing derivatives between pvs - pvEdgeDerivativeFactor2 = 0.666 # GRC fudge factor: factor reducing derivatives beneath lpv - laNodeId = [ [], [] ] - raNodeId = [ [], [] ] - ran1FreeWallStart = elementsCountAroundAtrialSeptum - 1 - ran1MidFreeWall = ran1FreeWallStart + n1MidFreeWall + for n1 in range(1, len(ravtx[n3]) - 1): + if ran1raap < n1 <= ran1Aorta: + # right atrial appendage; aorta substituted from ract later + ravtNodeId[n3].append(None) + continue + node = nodes.createNode(nodeIdentifier, nodetemplate) + ravtNodeId[n3].append(nodeIdentifier) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, ravtx [n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, ravtd1[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, ravtd2[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, ravtd3[n3][n1]) + nodeIdentifier += 1 + ravtNodeId[0].append(asNodeId[1][elementsCountAroundAtrialSeptum]) + ravtNodeId[1].append(agNodeId[1]) + + # create nodes on left atrium over appendage + agn1 = 2 if commonLeftRightPvOstium else 1 + asn1 = (elementsCountAroundAtrialSeptum + 1) if commonLeftRightPvOstium else elementsCountAroundAtrialSeptum + laoaNodeId = [ [ asNodeId[0][asn1] ], [ agNodeId[agn1] ] ] for n3 in range(2): - for n2 in range(elementsCountUpAtria + 1): - if n3 == 0: - lax = laInnerx[n2] - lad1 = laInnerd1[n2] - lad2 = laInnerd2[n2] - lad3 = laInnerd3[n2] - else: - lax = laOuterx[n2] - lad1 = laOuterd1[n2] - lad2 = laOuterd2[n2] - lad3 = laOuterd3[n2] - - # left atrium - aNodeId = [ None ]*elementsCountAroundAtria - for n1 in range(elementsCountAroundAtria): - if (n2 == elementsCountUpAtria) and (n1 > elementsCountRidgeVenous) and (n1 <= elementsCountAroundAtrialFreeWall): - if (n1 < (elementsCountAroundAtrialFreeWall - elementsCountRidgeVenous)): - aNodeId[n1] = aNodeId[elementsCountRidgeVenous] - else: - aNodeId[n1] = aNodeId[elementsCountAroundAtrialFreeWall - n1] - continue - if lax[n1] is None: - continue - if addInlets: - if (n1 > lipve1min) and (n1 <= lipve1max) and (n2 > lipve2min) and (n2 <= lipve2max): - continue # lipv inlet location - if (n1 > lspve1min) and (n1 <= lspve1max) and (n2 > lspve2min) and (n2 <= lspve2max): - continue # lspv inlet location - if (n1 > ripve1min) and (n1 <= ripve1max) and (n2 > ripve2min) and (n2 <= ripve2max): - continue # ripv inlet location - if (n1 > rspve1min) and (n1 <= rspve1max) and (n2 > rspve2min) and (n2 <= rspve2max): - continue # rspv inlet location + for n1 in range(1, len(laoax[n3]) - 1): + node = nodes.createNode(nodeIdentifier, nodetemplate) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, laoax [n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, laoad1[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, laoad2[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, laoad3[n3][n1]) + laoaNodeId[n3].append(nodeIdentifier) + nodeIdentifier += 1 + laoaNodeId[n3].append(lavtNodeId[n3][lan1Mid]) + if not commonLeftRightPvOstium: + # use second laoa node as aorta vestibule top node + lavtNodeId[n3][lan1Aorta] = laoaNodeId[n3][1] + + if not commonLeftRightPvOstium: + # create nodes on row above left atrium venous anterior to LPV ostium + lavbNodeId = [ [ asNodeId[0][asn1va] ], [ agNodeId[elementsCountOverLeftAtriumNonVenousAnterior] ] ] + n1lpv = -elementsCountOverLeftAtriumVenous//2 + for n3 in range(2): + for n1 in range(1, len(lavbx[n3]) - 1): node = nodes.createNode(nodeIdentifier, nodetemplate) - aNodeId[n1] = nodeIdentifier cache.setNode(node) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, lax[n1]) - # reduce edge derivative between and below LPV inlets to better fit them: - if (n1 == n1MidFreeWall) and (n2 > lipve2min): - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, [ pvEdgeDerivativeFactor1*d for d in lad1[n1]]) - else: - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, lad1[n1]) - if (n2 == elementsCountUpAtria) and (n1 < elementsCountRidgeVenous): - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, [ pvEdgeDerivativeFactor1*d for d in lad2[n1]]) - #elif (n2 == lipve2min) and (n1 > lspve1min) and (n1 <= lipve1max): - # coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, [ pvEdgeDerivativeFactor2*d for d in lad2[n1]]) - else: - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, lad2[n1]) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, lad3[n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, lavbx [n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, lavbd1[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, lavbd2[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, lavbd3[n3][n1]) + lavbNodeId[n3].append(nodeIdentifier) nodeIdentifier += 1 - laNodeId[n3].append(aNodeId) - - # right atrium - aNodeId = [ None ]*elementsCountAroundAtria - for n1 in range(elementsCountAroundAtria): - n1l = elementsCountAroundAtria - 1 - n1 - if (n3 == 1) and ((n1l == 0) or (n1l == elementsCountAroundAtrialFreeWall)): - aNodeId[n1] = laNodeId[n3][n2][n1l] - continue - if (n2 == elementsCountUpAtria) and (n1l > elementsCountRidgeVenous) and (n1l <= elementsCountAroundAtrialFreeWall): - continue # copy from anterior, below, once created - if lax[n1l] is None: - continue - if addInlets: - if (n1 > (ran1FreeWallStart + ivce1min)) and (n1 <= (ran1FreeWallStart + ivce1max)) and (n2 > ivce2min) and (n2 <= ivce2max): - continue # ivc inlet location - if (n1 > (ran1FreeWallStart + svce1min)) and (n1 <= (ran1FreeWallStart + svce1max)) and (n2 > svce2min) and (n2 <= svce2max): - continue # svc inlet location + lavbNodeId[n3].append(lpvoNodeId[n3][n1lpv]) + + # create nodes on row above left atrium venous posterior to LPV ostium + lavqNodeId = [ [ asNodeId[0][-1] ], [ agNodeId[elementsCountOverLeftAtriumNonVenousAnterior + elementsCountOverLeftAtriumVenous ] ] ] + n1lpv = elementsCountOverLeftAtriumVenous//2 + for n3 in range(2): + for n1 in range(1, len(lavqx[n3]) - 1): + node = nodes.createNode(nodeIdentifier, nodetemplate) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, lavqx [n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, lavqd1[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, lavqd2[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, lavqd3[n3][n1]) + lavqNodeId[n3].append(nodeIdentifier) + nodeIdentifier += 1 + if commonLeftRightPvOstium: + lavqNodeId[n3].append(laoaNodeId[n3][-2]) + else: + lavqNodeId[n3].append(lpvoNodeId[n3][n1lpv]) + + if not commonLeftRightPvOstium: + # create left atrium venous midline nodes + lamlNodeId = [ [], [] ] + for n3 in range(2): + lamlNodeId[n3].append(lavbNodeId[n3][elementsCountAroundLeftAtriumRPV]) + for n2 in range(1, len(lamlx[n3]) - 1): node = nodes.createNode(nodeIdentifier, nodetemplate) - aNodeId[n1] = nodeIdentifier cache.setNode(node) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, [ -lax[n1l][0], lax[n1l][1], lax[n1l][2] ]) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, [ lad1[n1l][0], -lad1[n1l][1], -lad1[n1l][2] ]) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, [ -lad2[n1l][0], lad2[n1l][1], lad2[n1l][2] ]) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, [ -lad3[n1l][0], lad3[n1l][1], lad3[n1l][2] ]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, lamlx [n3][n2]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, lamld1[n3][n2]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, lamld2[n3][n2]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, lamld3[n3][n2]) + lamlNodeId[n3].append(nodeIdentifier) nodeIdentifier += 1 - if n2 == elementsCountUpAtria: - # fix up posterior ridge and end nodes - for n1 in range(ran1FreeWallStart, ran1FreeWallStart + elementsCountRidgeVenous + 1): - aNodeId[n1] = aNodeId[elementsCountAroundAtria + ran1FreeWallStart - n1 - 1] - for n1 in range(ran1FreeWallStart + elementsCountRidgeVenous + 1, ran1FreeWallStart + elementsCountAroundAtrialFreeWall - elementsCountRidgeVenous): - aNodeId[n1] = aNodeId[ran1FreeWallStart + elementsCountRidgeVenous] - raNodeId[n3].append(aNodeId) - - # add nodes to left/right fibrous ring groups so heart1 can find them - for i in range(2): - fibrousRingGroup = lFibrousRingGroup if (i == 0) else rFibrousRingGroup - fibrousRingNodesetGroup = fibrousRingGroup.getNodesetGroup(nodes) - for n3 in range(2): - aNodeId = laNodeId[n3][0] if (i == 0) else raNodeId[n3][0] - for n1 in range(elementsCountAroundAtria): - if aNodeId[n1]: - node = nodes.findNodeByIdentifier(aNodeId[n1]) - fibrousRingNodesetGroup.addNode(node) + lamlNodeId[n3].append(lavqNodeId[n3][elementsCountAroundLeftAtriumRPV]) - # create fossa ovalis nodes - fossaCentreNodeId = [] - fossaNodeId = [ [], [] ] + # create right atrium crista terminalis nodes + ractNodeId = [ [], [] ] for n3 in range(2): - node = nodes.createNode(nodeIdentifier, nodetemplate) - cache.setNode(node) - result = coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, fossaCentrex[n3]) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, fossaCentred1[n3]) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, fossaCentred2[n3]) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, fossaCentred3[n3]) - fossaCentreNodeId.append(nodeIdentifier) - nodeIdentifier += 1 - for nf in range(elementsCountAroundFossa): + ractNodeId[n3].append(agNodeId[1]) + for n1 in range(1, len(ractx[n3]) - 1): node = nodes.createNode(nodeIdentifier, nodetemplate) cache.setNode(node) - result = coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, fossax[n3][nf]) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, fossad1[n3][nf]) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, fossad2[n3][nf]) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, fossad3[n3][nf]) - fossaNodeId[n3].append(nodeIdentifier) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, ractx [n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, ractd1[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, ractd2[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, ractd3[n3][n1]) + ractNodeId[n3].append(nodeIdentifier) nodeIdentifier += 1 + ractNodeId[n3].append(ravtNodeId[n3][ran1Ctp]) + # use second ract node as aorta vestibule top node + ravtNodeId[n3][ran1Aorta] = ractNodeId[n3][1] - ################# - # Create elements - ################# + # create right atrium venous midline nodes + ravmNodeId = [ [], [] ] + for n3 in range(2): + ravmNodeId[n3].append(ractNodeId[n3][elementsCountOverCristaTerminalisAnterior]) + for n1 in range(1, len(ravmx[n3]) - 1): + node = nodes.createNode(nodeIdentifier, nodetemplate) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, ravmx [n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, ravmd1[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, ravmd2[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, ravmd3[n3][n1]) + ravmNodeId[n3].append(nodeIdentifier) + nodeIdentifier += 1 + ravmNodeId[0].append(asNodeId[1][asn1Mid]) + ravmNodeId[1].append(agNodeId[agn1Mid]) - mesh = fm.findMeshByDimension(3) + # create right atrial appendage plain boundary nodes + raapNodeId = [ [], [] ] + for n3 in range(2): + raapNodeId[n3].append(ractNodeId[n3][2]) + for n1 in range(1, len(raapx[n3]) - 1): + node = nodes.createNode(nodeIdentifier, nodetemplate) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, raapx [n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, raapd1[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, raapd2[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, raapd3[n3][n1]) + raapNodeId[n3].append(nodeIdentifier) + nodeIdentifier += 1 + raapNodeId[n3].append(ravtNodeId[n3][ran1raap]) + # and middle row on plain appendage + raaqNodeId = [ [], [] ] + for n3 in range(2): + raaqNodeId[n3].append(ractNodeId[n3][2]) + for n1 in range(1, len(raaqx[n3]) - 1): + node = nodes.createNode(nodeIdentifier, nodetemplate) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, raaqx [n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, raaqd1[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, raaqd2[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, raaqd3[n3][n1]) + raaqNodeId[n3].append(nodeIdentifier) + nodeIdentifier += 1 + raaqNodeId[n3].append(ravtNodeId[n3][ran1raaq]) - elementIdentifier = max(1, zinc_utils.getMaximumElementIdentifier(mesh) + 1) + if False: + # create lt nodes: + for n1 in range(len(ltBaseOuterx)): + node = nodes.createNode(nodeIdentifier, nodetemplate) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, ltBaseOuterx [n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, ltBaseOuterd1[n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, ltBaseOuterd2[n1]) + nodeIdentifier += 1 - laMeshGroup = laGroup.getMeshGroup(mesh) - raMeshGroup = raGroup.getMeshGroup(mesh) - aSeptumMeshGroup = aSeptumGroup.getMeshGroup(mesh) - fossaMeshGroup = fossaGroup.getMeshGroup(mesh) - lipvMeshGroup = lipvGroup.getMeshGroup(mesh) - lspvMeshGroup = lspvGroup.getMeshGroup(mesh) - ripvMeshGroup = ripvGroup.getMeshGroup(mesh) - rspvMeshGroup = rspvGroup.getMeshGroup(mesh) - ivcInletMeshGroup = ivcInletGroup.getMeshGroup(mesh) - svcInletMeshGroup = svcInletGroup.getMeshGroup(mesh) + drawLaTrackSurface = False + if drawLaTrackSurface: + # create track surface nodes: + laTrackSurfaceFirstNodeIdentifier = nodeIdentifier + for n in range((laTrackSurface.elementsCount2 + 1)*(laTrackSurface.elementsCount1 + 1)): + node = nodes.createNode(nodeIdentifier, nodetemplate) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, laTrackSurface.nx[n] ) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, laTrackSurface.nd1[n]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, laTrackSurface.nd2[n]) + nodeIdentifier += 1 + drawRaTrackSurface = False + if drawRaTrackSurface: + # create track surface nodes: + raTrackSurfaceFirstNodeIdentifier = nodeIdentifier + for n in range((raTrackSurface.elementsCount2 + 1)*(raTrackSurface.elementsCount1 + 1)): + node = nodes.createNode(nodeIdentifier, nodetemplate) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, raTrackSurface.nx[n] ) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, raTrackSurface.nd1[n]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, raTrackSurface.nd2[n]) + nodeIdentifier += 1 + + ################# + # Create elements + ################# tricubichermite = eftfactory_tricubichermite(mesh, useCrossDerivatives) tricubicHermiteBasis = fm.createElementbasis(3, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE) @@ -902,347 +2085,254 @@ def generateBaseMesh(cls, region, options): elementtemplateX = mesh.createElementtemplate() elementtemplateX.setElementShapeType(Element.SHAPE_TYPE_CUBE) - # create outside wall elements + # left atrium free wall elements to vestibule top, starting at cfb / anterior interatrial sulcus + eftBaseSulcus = tricubichermite.createEftNoCrossDerivatives() + setEftScaleFactorIds(eftBaseSulcus, [1], []) + remapEftNodeValueLabel(eftBaseSulcus, [ 1 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) + remapEftNodeValueLabel(eftBaseSulcus, [ 2 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) + remapEftNodeValueLabel(eftBaseSulcus, [ 3 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) + remapEftNodeValueLabel(eftBaseSulcus, [ 3 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS3, [] ) ]) + remapEftNodeValueLabel(eftBaseSulcus, [ 4 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS3, [1] ) ]) + remapEftNodeValueLabel(eftBaseSulcus, [ 4 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) + remapEftNodeValueLabel(eftBaseSulcus, [ 5, 6, 7, 8 ], Node.VALUE_LABEL_D_DS1, []) + remapEftNodeValueLabel(eftBaseSulcus, [ 5, 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ]) + remapEftNodeValueLabel(eftBaseSulcus, [ 6, 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) + remapEftNodeValueLabel(eftBaseSulcus, [ 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) + ln_map = [ 1, 2, 3, 4, 5, 5, 6, 6 ] + remapEftLocalNodes(eftBaseSulcus, 6, ln_map) + # general linear map d3 adjacent to collapsed cfb/crux + eftBaseSulcusNext = tricubichermite.createEftNoCrossDerivatives() + setEftScaleFactorIds(eftBaseSulcusNext, [1], []) + remapEftNodeValueLabel(eftBaseSulcusNext, [ 3 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ]) + remapEftNodeValueLabel(eftBaseSulcusNext, [ 5, 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) + # general linear map d3 adjacent to collapsed cfb/crux + eftBaseSulcusPrev = tricubichermite.createEftNoCrossDerivatives() + setEftScaleFactorIds(eftBaseSulcusPrev, [1], []) + remapEftNodeValueLabel(eftBaseSulcusPrev, [ 4 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) + remapEftNodeValueLabel(eftBaseSulcusPrev, [ 6, 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ]) + for e1 in range(-1, elementsCountAroundLeftAtriumFreeWall): + eft1 = eft + elementtemplate1 = elementtemplate + nids = [ + labNodeId[0][e1], labNodeId[0][e1 + 1], lavtNodeId[0][e1], lavtNodeId[0][e1 + 1], + labNodeId[1][e1], labNodeId[1][e1 + 1], lavtNodeId[1][e1], lavtNodeId[1][e1 + 1]] + if None in nids: + continue # left atrial appendage + scalefactors = None + meshGroups = [ laMeshGroup ] + if e1 == -1: + # cfb/anterior interatrial groove straddles left and right atria, collapsed to 6 node wedge + nids[0] = rabNodeId[0][-elementsCountAroundAtrialSeptum] + nids[2] = ravtNodeId[0][-1] + nids.pop(6) + nids.pop(4) + meshGroups += [ raMeshGroup ] + eft1 = eftBaseSulcus + scalefactors = [ -1.0 ] + elif e1 == 0: + eft1 = eftBaseSulcusNext + scalefactors = [ -1.0 ] + elif e1 == (elementsCountAroundLeftAtriumFreeWall - 1): + eft1 = eftBaseSulcusPrev + scalefactors = [ -1.0 ] + if eft1 is not eft: + elementtemplateX.defineField(coordinates, -1, eft1) + elementtemplate1 = elementtemplateX + element = mesh.createElement(elementIdentifier, elementtemplate1) + result2 = element.setNodesByIdentifier(eft1, nids) + if scalefactors: + result3 = element.setScaleFactors(eft1, scalefactors) + else: + result3 = '-' + #print('create element la', element.isValid(), elementIdentifier, result2, result3, nids) + elementIdentifier += 1 + for meshGroup in meshGroups: + meshGroup.addElement(element) - for e2 in range(elementsCountUpAtria): + # right atrium free wall elements to vestibule top, starting at crux / posterior interatrial sulcus + for e1 in range(-1, elementsCountAroundRightAtriumFreeWall): + eft1 = eft + elementtemplate1 = elementtemplate + nids = [ + rabNodeId[0][e1], rabNodeId[0][e1 + 1], ravtNodeId[0][e1], ravtNodeId[0][e1 + 1], + rabNodeId[1][e1], rabNodeId[1][e1 + 1], ravtNodeId[1][e1], ravtNodeId[1][e1 + 1]] + if None in nids: + continue # right atrial appendage + scalefactors = None + meshGroups = [ raMeshGroup ] + if (e1 >= elementsCountAroundRightAtriumPosteriorVenous) and (e1 < elementsCountAroundRightAtriumFreeWall - elementsCountAroundRightAtriumAorta - 1): + meshGroups += [ raaMeshGroup ] + if e1 == -1: + # crux/posterior interatrial groove straddles left and right atria, collapsed to 6 node wedge + nids[0] = labNodeId[0][elementsCountAroundLeftAtriumFreeWall] + nids[2] = lavtNodeId[0][elementsCountAroundLeftAtriumFreeWall] + nids.pop(6) + nids.pop(4) + meshGroups += [ laMeshGroup ] + eft1 = eftBaseSulcus + scalefactors = [ -1.0 ] + elif e1 == 0: + eft1 = eftBaseSulcusNext + scalefactors = [ -1.0 ] + elif e1 == (elementsCountAroundRightAtriumFreeWall - 1): + # similar to eftBaseSulcusPrev, but general linear map node from crista terminalis + # general linear map d3 adjacent to collapsed cfb/crux + eft1 = tricubichermite.createEftNoCrossDerivatives() + setEftScaleFactorIds(eft1, [1], []) + remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [1] ) ]) + remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ) ]) # GRC , ( Node.VALUE_LABEL_D_DS2, []) ]) + remapEftNodeValueLabel(eft1, [ 4 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) + remapEftNodeValueLabel(eft1, [ 6, 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ]) + scalefactors = [ -1.0 ] + if eft1 is not eft: + elementtemplateX.defineField(coordinates, -1, eft1) + elementtemplate1 = elementtemplateX + element = mesh.createElement(elementIdentifier, elementtemplate1) + result2 = element.setNodesByIdentifier(eft1, nids) + if scalefactors: + result3 = element.setScaleFactors(eft1, scalefactors) + else: + result3 = '-' + #print('create element ra', element.isValid(), elementIdentifier, result2, result3, nids) + elementIdentifier += 1 + for meshGroup in meshGroups: + meshGroup.addElement(element) - # left atrium, starting at cfb / anterior interatrial sulcus - for e1 in range(-1, elementsCountAroundAtrialFreeWall): + if commonLeftRightPvOstium: + # left atrium extra + meshGroups = [ laMeshGroup ] + for e1 in range(elementsCountAroundLeftAtriumAorta): eft1 = eft elementtemplate1 = elementtemplate - nids = [ - laNodeId[0][e2][e1], laNodeId[0][e2][e1 + 1], laNodeId[0][e2 + 1][e1], laNodeId[0][e2 + 1][e1 + 1], - laNodeId[1][e2][e1], laNodeId[1][e2][e1 + 1], laNodeId[1][e2 + 1][e1], laNodeId[1][e2 + 1][e1 + 1]] + nids = [ lavtNodeId[0][e1], lavtNodeId[0][e1 + 1], laoaNodeId[0][e1], laoaNodeId[0][e1 + 1], + lavtNodeId[1][e1], lavtNodeId[1][e1 + 1], laoaNodeId[1][e1], laoaNodeId[1][e1 + 1] ] scalefactors = None - meshGroups = [ laMeshGroup ] - - if addInlets: - if (e1 >= lipve1min) and (e1 <= lipve1max) and (e2 >= lipve2min) and (e2 <= lipve2max): - continue # lipv inlet location - if (e1 >= lspve1min) and (e1 <= lspve1max) and (e2 >= lspve2min) and (e2 <= lspve2max): - continue # lspv inlet location - if (e1 >= ripve1min) and (e1 <= ripve1max) and (e2 >= ripve2min) and (e2 <= ripve2max): - continue # ripv inlet location - if (e1 >= rspve1min) and (e1 <= rspve1max) and (e2 >= rspve2min) and (e2 <= rspve2max): - continue # rspv inlet location - if e1 == -1: - # cfb/anterior interatrial groove straddles left and right atria, collapsed to 6 node wedge - nids[0] = raNodeId[0][e2][-1] - nids[2] = raNodeId[0][e2 + 1][-1] - nids.pop(6) - nids.pop(4) - meshGroups += [ raMeshGroup ] - eft1 = tricubichermite.createEftNoCrossDerivatives() - setEftScaleFactorIds(eft1, [1], []) - remapEftNodeValueLabel(eft1, [ 1, 3 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) - remapEftNodeValueLabel(eft1, [ 2, 4 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) - remapEftNodeValueLabel(eft1, [ 5, 6, 7, 8 ], Node.VALUE_LABEL_D_DS1, []) - remapEftNodeValueLabel(eft1, [ 5, 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ]) - remapEftNodeValueLabel(eft1, [ 6, 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) - ln_map = [ 1, 2, 3, 4, 5, 5, 6, 6 ] - remapEftLocalNodes(eft1, 6, ln_map) - elif e1 == 0: - # general linear map d3 adjacent to collapsed cfb + if e1 == 0: + # general linear map d3 adjacent to interatrial groove eft1 = tricubichermite.createEftNoCrossDerivatives() setEftScaleFactorIds(eft1, [1], []) + remapEftNodeValueLabel(eft1, [ 1, 3 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ]) remapEftNodeValueLabel(eft1, [ 5, 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) - elif e1 == (elementsCountAroundAtrialFreeWall - 1): - # general linear map d3 adjacent to collapsed crux - eft1 = tricubichermite.createEftNoCrossDerivatives() - if e2 == (elementsCountUpAtria - 1): - # reverse d1 and d2 on ridge - setEftScaleFactorIds(eft1, [1], []) - scaleEftNodeValueLabels(eft1, [ 3, 4, 7, 8 ], [ Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS2 ], [ 1 ]) - remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ]) - remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) - else: - remapEftNodeValueLabel(eft1, [ 6, 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ]) - elif (e2 == (elementsCountUpAtria - 1)) and (e1 >= elementsCountRidgeVenous) and \ - (e1 < (elementsCountAroundAtrialFreeWall - elementsCountRidgeVenous)): - # 6 node pole elements - nids.pop(7) - nids.pop(3) - s = e1 - elementsCountRidgeVenous - radiansAround0 = 0.5*math.pi + s*radiansPerElementAroundEnd - radiansAround1 = radiansAround0 + radiansPerElementAroundEnd - # scale factor identifiers follow convention of offsetting by 100 for each 'version' - eft1 = tricubichermite.createEftShellPoleTop(s*100, (s + 1)*100) - scalefactors = [ - -1.0, - math.cos(radiansAround0), math.sin(radiansAround0), radiansPerElementAroundEnd, - math.cos(radiansAround1), math.sin(radiansAround1), radiansPerElementAroundEnd, - math.cos(radiansAround0), math.sin(radiansAround0), radiansPerElementAroundEnd, - math.cos(radiansAround1), math.sin(radiansAround1), radiansPerElementAroundEnd - ] - elif (e2 == (elementsCountUpAtria - 1)) and (e1 > (elementsCountAroundAtrialFreeWall//2)): - # reverse d1 and d2 on ridge - eft1 = tricubichermite.createEftNoCrossDerivatives() - setEftScaleFactorIds(eft1, [1], []) - scaleEftNodeValueLabels(eft1, [ 3, 4, 7, 8 ], [ Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS2 ], [ 1 ]) - - if eft1 is not eft: + scalefactors = [ -1.0 ] elementtemplateX.defineField(coordinates, -1, eft1) elementtemplate1 = elementtemplateX - element = mesh.createElement(elementIdentifier, elementtemplate1) result2 = element.setNodesByIdentifier(eft1, nids) if scalefactors: result3 = element.setScaleFactors(eft1, scalefactors) - elif eft1.getNumberOfLocalScaleFactors() == 1: - result3 = element.setScaleFactors(eft1, [ -1.0 ]) else: - result3 = ' ' - #print('create element la', element.isValid(), elementIdentifier, result2, result3, nids) + result3 = '-' + #print('create element laao', element.isValid(), elementIdentifier, result2, result3, nids) elementIdentifier += 1 - for meshGroup in meshGroups: meshGroup.addElement(element) - - # right atrium, starting at crux / posterior interatrial sulcus - for e1 in range(-1, elementsCountAroundAtrialFreeWall): - n1 = ran1FreeWallStart + e1 + else: # not commonLeftRightPvOstium: + # left atrium first row of elements above appendage, anterior + meshGroups = [ laMeshGroup ] + for e1 in range(elementsCountAroundLeftAtriumRPV): eft1 = eft elementtemplate1 = elementtemplate - nids = [ - raNodeId[0][e2][n1], raNodeId[0][e2][n1 + 1], raNodeId[0][e2 + 1][n1], raNodeId[0][e2 + 1][n1 + 1], - raNodeId[1][e2][n1], raNodeId[1][e2][n1 + 1], raNodeId[1][e2 + 1][n1], raNodeId[1][e2 + 1][n1 + 1]] + nids = [ laoaNodeId[0][e1], laoaNodeId[0][e1 + 1], lavbNodeId[0][e1], lavbNodeId[0][e1 + 1], + laoaNodeId[1][e1], laoaNodeId[1][e1 + 1], lavbNodeId[1][e1], lavbNodeId[1][e1 + 1] ] scalefactors = None - meshGroups = [ raMeshGroup ] - - if addInlets: - if (e1 >= ivce1min) and (e1 <= ivce1max) and (e2 >= ivce2min) and (e2 <= ivce2max): - continue # ivc inlet location - if (e1 >= svce1min) and (e1 <= svce1max) and (e2 >= svce2min) and (e2 <= svce2max): - continue # svc inlet location - if e1 == -1: - # crux/posterior interatrial groove straddles left and right atria, collapsed to 6 node wedge - nids[0] = laNodeId[0][e2][elementsCountAroundAtrialFreeWall] - nids[2] = laNodeId[0][e2 + 1][elementsCountAroundAtrialFreeWall] - nids.pop(6) - nids.pop(4) - meshGroups += [ laMeshGroup ] + if e1 == 0: + # general linear map d3 adjacent to interatrial groove eft1 = tricubichermite.createEftNoCrossDerivatives() setEftScaleFactorIds(eft1, [1], []) - remapEftNodeValueLabel(eft1, [ 1, 3 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) - remapEftNodeValueLabel(eft1, [ 2, 4 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) - remapEftNodeValueLabel(eft1, [ 5, 6, 7, 8 ], Node.VALUE_LABEL_D_DS1, []) - remapEftNodeValueLabel(eft1, [ 5, 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ]) - if e2 == (elementsCountUpAtria - 1): - # reverse D_DS1, D_DS2 on ridge, avoiding scaling D_DS1 by -1 twice at node 8 - scaleEftNodeValueLabels(eft1, [ 3, 4, 7 ], [ Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS2 ], [ 1 ]) - scaleEftNodeValueLabels(eft1, [ 8 ], [ Node.VALUE_LABEL_D_DS2 ], [ 1 ]) - remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) - remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ]) - else: - remapEftNodeValueLabel(eft1, [ 6, 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) - ln_map = [ 1, 2, 3, 4, 5, 5, 6, 6 ] - remapEftLocalNodes(eft1, 6, ln_map) - elif e1 == 0: - # general linear map d3 adjacent to collapsed crux - eft1 = tricubichermite.createEftNoCrossDerivatives() - setEftScaleFactorIds(eft1, [1], []) - if e2 == (elementsCountUpAtria - 1): - # reverse d1 and d2 on ridge, avoiding scaling D_DS1 by -1 twice at node 7 - setEftScaleFactorIds(eft1, [1], []) - scaleEftNodeValueLabels(eft1, [ 3, 4, 7, 8 ], [ Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS2 ], [ 1 ]) - remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) - remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ]) - else: - remapEftNodeValueLabel(eft1, [ 5, 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) - elif e1 == (elementsCountAroundAtrialFreeWall - 1): - # general linear map d3 adjacent to collapsed cfb - eft1 = tricubichermite.createEftNoCrossDerivatives() - remapEftNodeValueLabel(eft1, [ 6, 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ]) - elif (e2 == (elementsCountUpAtria - 1)) and (e1 >= elementsCountRidgeVenous) and \ - (e1 < (elementsCountAroundAtrialFreeWall - elementsCountRidgeVenous)): - # 6 node pole elements - nids.pop(7) - nids.pop(3) - s = e1 - elementsCountRidgeVenous - radiansAround0 = -0.5*math.pi + s*radiansPerElementAroundEnd - radiansAround1 = radiansAround0 + radiansPerElementAroundEnd - # scale factor identifiers follow convention of offsetting by 100 for each 'version' - eft1 = tricubichermite.createEftShellPoleTop(s*100, (s + 1)*100) - scalefactors = [ - -1.0, - math.cos(radiansAround0), math.sin(radiansAround0), radiansPerElementAroundEnd, - math.cos(radiansAround1), math.sin(radiansAround1), radiansPerElementAroundEnd, - math.cos(radiansAround0), math.sin(radiansAround0), radiansPerElementAroundEnd, - math.cos(radiansAround1), math.sin(radiansAround1), radiansPerElementAroundEnd - ] - elif (e2 == (elementsCountUpAtria - 1)) and (e1 < (elementsCountAroundAtrialFreeWall//2)): - # reverse d1 and d2 on ridge - eft1 = tricubichermite.createEftNoCrossDerivatives() - setEftScaleFactorIds(eft1, [1], []) - scaleEftNodeValueLabels(eft1, [ 3, 4, 7, 8 ], [ Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS2 ], [ 1 ]) - - if eft1 is not eft: + remapEftNodeValueLabel(eft1, [ 1, 3 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ]) + remapEftNodeValueLabel(eft1, [ 5, 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) + scalefactors = [ -1.0 ] elementtemplateX.defineField(coordinates, -1, eft1) elementtemplate1 = elementtemplateX - element = mesh.createElement(elementIdentifier, elementtemplate1) result2 = element.setNodesByIdentifier(eft1, nids) if scalefactors: result3 = element.setScaleFactors(eft1, scalefactors) - elif eft1.getNumberOfLocalScaleFactors() == 1: - result3 = element.setScaleFactors(eft1, [ -1.0 ]) else: - result3 = ' ' - #print('create element ra', element.isValid(), elementIdentifier, result2, result3, nids) + result3 = '-' + #print('create element lavb', element.isValid(), elementIdentifier, result2, result3, nids) elementIdentifier += 1 - for meshGroup in meshGroups: meshGroup.addElement(element) - # create first row of septum elements - - meshGroups = [ laMeshGroup, raMeshGroup, aSeptumMeshGroup ] - for e1 in range(elementsCountFossaBase): - n1l = elementsCountAroundAtrialFreeWall + e1 - elementsCountAroundAtria - n1r = ran1FreeWallStart - e1 - if (elementsCountAroundAtrialSeptum == 2) and (e1 > 1): - n1l -= 1 - n1r += 1 - nf1 = e1 - 1 - nf2 = e1 - nids = [ laNodeId[0][0][n1l], laNodeId[0][0][n1l + 1], fossaNodeId[0][nf1], fossaNodeId[0][nf2], \ - raNodeId[0][0][n1r], raNodeId[0][0][n1r - 1], fossaNodeId[1][nf1], fossaNodeId[1][nf2] ] + # left atrium first row of elements above vestibule, posterior + meshGroups = [ laMeshGroup ] + scalefactors = [ -1.0 ] + elementsCount = len(lavqx[1]) - 1 if commonLeftRightPvOstium else elementsCountAroundLeftAtriumRPV + for e1 in range(elementsCount): + nc = elementsCountAroundLeftAtriumFreeWall - e1 + nids = [ lavqNodeId[0][e1], lavqNodeId[0][e1 + 1], lavtNodeId[0][nc], lavtNodeId[0][nc - 1], + lavqNodeId[1][e1], lavqNodeId[1][e1 + 1], lavtNodeId[1][nc], lavtNodeId[1][nc - 1] ] eft1 = tricubichermite.createEftNoCrossDerivatives() setEftScaleFactorIds(eft1, [1], []) + scaleEftNodeValueLabels(eft1, [ 3, 4, 7, 8 ], [ Node.VALUE_LABEL_D_DS1 ], [ 1 ]) + scaleEftNodeValueLabels(eft1, [ 3, 4, 7, 8 ], [ Node.VALUE_LABEL_D_DS2 ], [ 1 ]) if e1 == 0: - nids[2] = laNodeId[0][1][n1l] - nids[6] = raNodeId[0][1][n1r] - scaleEftNodeValueLabels(eft1, [ 5, 6, 7 ], [ Node.VALUE_LABEL_D_DS1 ], [ 1 ]) - scaleEftNodeValueLabels(eft1, [ 6 ], [ Node.VALUE_LABEL_D_DS3 ], [ 1 ]) - remapEftNodeValueLabel(eft1, [ 1, 3 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) - remapEftNodeValueLabel(eft1, [ 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, [] ) ]) - remapEftNodeValueLabel(eft1, [ 5, 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) - if elementsCountAroundAtrialSeptum == 2: - remapEftNodeValueLabel(eft1, [ 2 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) - remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) - elif e1 == (elementsCountFossaBase - 1): - nids[3] = laNodeId[0][1][n1l + 1] - nids[7] = raNodeId[0][1][n1r - 1] - scaleEftNodeValueLabels(eft1, [ 5, 6, 8 ], [ Node.VALUE_LABEL_D_DS1 ], [ 1 ]) - scaleEftNodeValueLabels(eft1, [ 5 ], [ Node.VALUE_LABEL_D_DS3 ], [ 1 ]) - remapEftNodeValueLabel(eft1, [ 2, 4 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) - remapEftNodeValueLabel(eft1, [ 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, [ 6, 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) - if elementsCountAroundAtrialSeptum == 2: - remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) - remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) - elif (elementsCountAroundAtrialSeptum == 2) and (e1 == 1): - # 6-node wedge element - nids.pop(5) - nids.pop(1) - scaleEftNodeValueLabels(eft1, [ 5, 6 ], [ Node.VALUE_LABEL_D_DS3 ], [ 1 ]) - remapEftNodeValueLabel(eft1, [ 1, 2, 5, 6 ], Node.VALUE_LABEL_D_DS1, []) - remapEftNodeValueLabel(eft1, [ 1, 6 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) - remapEftNodeValueLabel(eft1, [ 2, 5 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) - 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, [] ) ]) - ln_map = [ 1, 1, 2, 3, 4, 4, 5, 6 ] - remapEftLocalNodes(eft1, 6, ln_map) - else: - scaleEftNodeValueLabels(eft1, [ 5, 6 ], [ Node.VALUE_LABEL_D_DS1 ], [ 1 ]) - scaleEftNodeValueLabels(eft1, [ 5, 6 ], [ Node.VALUE_LABEL_D_DS3 ], [ 1 ]) - if e1 == 1: - remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) - if e1 == (elementsCountFossaBase - 2): - remapEftNodeValueLabel(eft1, [ 4, 8 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) - + # general linear map d3 adjacent to collapsed inter-atrial groove + remapEftNodeValueLabel(eft1, [ 1, 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, []) ]) + remapEftNodeValueLabel(eft1, [ 3, 5 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, []) ]) + elif commonLeftRightPvOstium and (e1 == (elementsCount - 1)): + remapEftNodeValueLabel(eft1, [ 2, 6 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [1]) ]) + remapEftNodeValueLabel(eft1, [ 2, 6 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, []) ]) elementtemplateX.defineField(coordinates, -1, eft1) - element = mesh.createElement(elementIdentifier, elementtemplateX) - #print('e1',e1,'nids',nids) + elementtemplate1 = elementtemplateX + element = mesh.createElement(elementIdentifier, elementtemplate1) result2 = element.setNodesByIdentifier(eft1, nids) - if eft1.getNumberOfLocalScaleFactors() == 1: - result3 = element.setScaleFactors(eft1, [ -1.0 ]) - #print('create element septum base', element.isValid(), elementIdentifier, result2, result3, nids) + result3 = element.setScaleFactors(eft1, scalefactors) + #print('create element lavq', element.isValid(), elementIdentifier, result2, result3, nids) elementIdentifier += 1 - for meshGroup in meshGroups: meshGroup.addElement(element) - # septum arch over fossa ovalis - + # create atrial septum base row of elements meshGroups = [ laMeshGroup, raMeshGroup, aSeptumMeshGroup ] - for e1 in range(2*(elementsCountUpAtria - 1)): + for e1 in range(elementsCountAroundAtrialSeptum): + n1l = elementsCountAroundLeftAtriumFreeWall + e1 - elementsCountAroundLeftAtrium + n1r = -e1 + nids = [ labNodeId[0][n1l], labNodeId[0][n1l + 1], asNodeId[0][e1], asNodeId[0][e1 + 1], \ + rabNodeId[0][n1r], rabNodeId[0][n1r - 1], asNodeId[1][e1], asNodeId[1][e1 + 1] ] eft1 = tricubichermite.createEftNoCrossDerivatives() setEftScaleFactorIds(eft1, [1], []) - nids = None - nf1 = e1 + elementsCountFossaBaseCentre - nf2 = (nf1 + 1) % elementsCountAroundFossa - if e1 < (elementsCountUpAtria - 1): - n2 = e1 + 1 - n1l = 0 - n1r = -1 - nids = [ laNodeId[0][n2][n1l], laNodeId[0][n2 + 1][n1l], fossaNodeId[0][nf1], fossaNodeId[0][nf2], \ - raNodeId[0][n2][n1r], raNodeId[0][n2 + 1][n1r], fossaNodeId[1][nf1], fossaNodeId[1][nf2] ] - # set derivative 1 after 2 to swap: - remapEftNodeValueLabel(eft1, [ 1, 2 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ) ]) - remapEftNodeValueLabel(eft1, [ 1, 2 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [] ) ]) - remapEftNodeValueLabel(eft1, [ 1, 2 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) - remapEftNodeValueLabel(eft1, [ 5, 6 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D2_DS1DS2, [] ) ]) # temporary to enable swap - remapEftNodeValueLabel(eft1, [ 5, 6 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ) ]) - remapEftNodeValueLabel(eft1, [ 5, 6 ], Node.VALUE_LABEL_D2_DS1DS2, [ ( Node.VALUE_LABEL_D_DS2, [] ) ]) # finish swap - remapEftNodeValueLabel(eft1, [ 5, 6 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) + scalefactors = [ -1.0 ] + scaleEftNodeValueLabels(eft1, [ 5, 6, 7, 8 ], [ Node.VALUE_LABEL_D_DS1 ], [ 1 ]) + if e1 == 0: + scaleEftNodeValueLabels(eft1, [ 6, 7, 8 ], [ Node.VALUE_LABEL_D_DS3 ], [ 1 ]) + remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) + remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) + elif e1 == (elementsCountAroundAtrialSeptum - 1): + scaleEftNodeValueLabels(eft1, [ 5, 7, 8 ], [ Node.VALUE_LABEL_D_DS3 ], [ 1 ]) + remapEftNodeValueLabel(eft1, [ 2 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) + remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) else: - n2 = 2*(elementsCountUpAtria - 1) - e1 + 1 - n1l = elementsCountAroundAtrialFreeWall - n1r = elementsCountAroundAtrialSeptum - 1 - nids = [ laNodeId[0][n2][n1l], laNodeId[0][n2 - 1][n1l], fossaNodeId[0][nf1], fossaNodeId[0][nf2], \ - raNodeId[0][n2][n1r], raNodeId[0][n2 - 1][n1r], fossaNodeId[1][nf1], fossaNodeId[1][nf2] ] - if n2 == elementsCountUpAtria: - remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ) ]) - remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [] ) ]) - remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) - remapEftNodeValueLabel(eft1, [ 2 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) - remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D2_DS1DS2, [] ) ]) # temporary to enable swap - remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ) ]) - remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D2_DS1DS2, [ ( Node.VALUE_LABEL_D_DS2, [] ) ]) # finish swap - remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) - remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) - lnl = [ 2 ] - lnr = [ 6 ] - else: - lnl = [ 1, 2 ] - lnr = [ 5, 6 ] - remapEftNodeValueLabel(eft1, lnl, Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [1] ) ]) - remapEftNodeValueLabel(eft1, lnl, Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ) ]) - remapEftNodeValueLabel(eft1, lnl, Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) - remapEftNodeValueLabel(eft1, lnr, Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [1] ) ]) - remapEftNodeValueLabel(eft1, lnr, Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ) ]) - remapEftNodeValueLabel(eft1, lnr, Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) - + scaleEftNodeValueLabels(eft1, [ 5, 6, 7, 8 ], [ Node.VALUE_LABEL_D_DS3 ], [ 1 ]) elementtemplateX.defineField(coordinates, -1, eft1) element = mesh.createElement(elementIdentifier, elementtemplateX) result2 = element.setNodesByIdentifier(eft1, nids) - result3 = element.setScaleFactors(eft1, [ -1.0 ]) - #print('create element septum arch', element.isValid(), elementIdentifier, result2, result3, nids) - elementIdentifier = elementIdentifier + 1 - + if eft1.getNumberOfLocalScaleFactors() == 1: + result3 = element.setScaleFactors(eft1, [ -1.0 ]) + #print('create element as base', element.isValid(), elementIdentifier, result2, result3, nids) + elementIdentifier += 1 for meshGroup in meshGroups: meshGroup.addElement(element) - # fossa ovalis elements - + # create fossa ovalis elements meshGroups = [ laMeshGroup, raMeshGroup, aSeptumMeshGroup, fossaMeshGroup ] - radiansAround0 = fossaRadiansAround[-1] - 2.0*math.pi + radiansAround0 = fossaRadiansAround[-1] + if radiansAround0 > fossaRadiansAround[0]: + radiansAround0 -= 2.0*math.pi + radiansAround1 = fossaRadiansAround[0] + radiansAround2 = fossaRadiansAround[1] + if radiansAround2 < radiansAround1: + radiansAround2 += 2.0*math.pi for e1 in range(elementsCountAroundFossa): va = e1 vb = (e1 + 1)%elementsCountAroundFossa eft1 = tricubichermite.createEftShellPoleTop(va*100, vb*100) elementtemplateX.defineField(coordinates, -1, eft1) element = mesh.createElement(elementIdentifier, elementtemplateX) - nids = [ fossaNodeId[0][va], fossaNodeId[0][vb], fossaCentreNodeId[0], fossaNodeId[1][va], fossaNodeId[1][vb], fossaCentreNodeId[1] ] + nids = [ foNodeId[0][va], foNodeId[0][vb], foCentreNodeId[0], foNodeId[1][va], foNodeId[1][vb], foCentreNodeId[1] ] result2 = element.setNodesByIdentifier(eft1, nids) - radiansAround1 = fossaRadiansAround[va] - radiansAround2 = fossaRadiansAround[vb] - if radiansAround2 < radiansAround1: - radiansAround2 += 2.0*math.pi - radiansAround3 = fossaRadiansAround[vb + 1 - elementsCountAroundFossa] + radiansAround3 = fossaRadiansAround[va + 2 - elementsCountAroundFossa] if radiansAround3 < radiansAround2: radiansAround3 += 2.0*math.pi dRadiansAround1 = 0.5*(radiansAround2 - radiansAround0) @@ -1257,520 +2347,821 @@ def generateBaseMesh(cls, region, options): result3 = element.setScaleFactors(eft1, scalefactors) #print('create element fossa', element.isValid(), elementIdentifier, result2, result3, nids) elementIdentifier = elementIdentifier + 1 - for meshGroup in meshGroups: meshGroup.addElement(element) - radiansAround0 = radiansAround1 - - # create pulmonary vein inlets to left atrium - - # GRC fudgefactors, multiple of inner radius that inlet centre is away from septum - rpvSeptumDistanceFactor = 2.0 - mx, md1 = interp.getCubicHermiteCurvesPointAtArcDistance(rx, rd1, 0.5*aSeptumThickness + rpvSeptumDistanceFactor*rpvInnerRadius + rx[0][0])[0:2] - laSeptumModX = aBaseInnerMajorMag*math.cos(aMajorAxisRadians)*math.cos(laSeptumRadians) \ - + aBaseInnerMinorMag*math.sin(aMajorAxisRadians)*math.sin(laSeptumRadians) - laOuterMajorx = [ aBaseOuterMajorMag*math.cos(aMajorAxisRadians), -aBaseOuterMajorMag*math.sin(aMajorAxisRadians), 0.0 ] - laOuterMinorx = [ aBaseOuterMinorMag*math.sin(aMajorAxisRadians), aBaseOuterMinorMag*math.cos(aMajorAxisRadians), 0.0 ] - rpvdx = laSeptumModX + mx[0] + 0.5*aSeptumThickness - rapvBaseRadians = getEllipseRadiansToX(laOuterMajorx[0], laOuterMinorx[0], rpvdx, laSeptumRadians + 0.5*math.pi) - rppvBaseRadians = getEllipseRadiansToX(laOuterMajorx[0], laOuterMinorx[0], rpvdx, laSeptumRadians - 0.5*math.pi) - - ax = [ laCentre[c] + laOuterMajorx[c]*math.cos(rapvBaseRadians) + laOuterMinorx[c]*math.sin(rapvBaseRadians) for c in range(3) ] - ad2 = [ 0.0, math.sin(aBaseFrontInclineRadians), math.cos(aBaseFrontInclineRadians) ] - bx = [ laCentre[c] + laOuterMajorx[c]*math.cos(rppvBaseRadians) + laOuterMinorx[c]*math.sin(rppvBaseRadians) for c in range(3) ] - bd2 = [ 0.0, math.sin(aBaseBackInclineRadians), -math.cos(aBaseBackInclineRadians) ] - md2 = [ 0.5*(bx[0] - ax[0]), 0.5*(bx[1] - ax[1]), 0.0 ] - - if False: - node = nodes.createNode(nodeIdentifier, nodetemplate) - cache.setNode(node) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, mx) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, md1) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, md2) - nodeIdentifier += 1 - - node = nodes.createNode(nodeIdentifier, nodetemplate) - cache.setNode(node) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, ax) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, ad2) - nodeIdentifier += 1 - - node = nodes.createNode(nodeIdentifier, nodetemplate) - cache.setNode(node) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, bx) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, bd2) - nodeIdentifier += 1 - - px, pd1 = interp.sampleCubicHermiteCurves([ ax, mx, bx ], [ ad2, md2, bd2 ], 2, - lengthFractionEnd = rpvPositionUp/(2.0 - rpvPositionUp), arcLengthDerivatives = True)[0:2] - rcpvx = px[1] - rcpvd1 = pd1[1] - - if False: - node = nodes.createNode(nodeIdentifier, nodetemplate) - cache.setNode(node) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, rcpvx) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, rcpvd1) - nodeIdentifier += 1 - - ex = interp.sampleCubicHermiteCurves([ laOuterx[0][n1MidFreeWall], vx ], - [ laOuterd2[0][n1MidFreeWall], vd2 ], elementsCountOut = 2, - lengthFractionStart = lpvPositionUp/(1.0 - lpvPositionUp), arcLengthDerivatives = True)[0] - lcpvx = ex[1] - - if False: - node = nodes.createNode(nodeIdentifier, nodetemplate) - cache.setNode(node) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, lcpvx) - nodeIdentifier += 1 - - lpvSpacingFactor = 2.0 if (lpvCount == 2) else 0.0 - rpvSpacingFactor = 2.0 if (rpvCount == 2) else 0.0 - pvDerivativeFactor = 1.0/elementsCountInlet # GRC fudge factor - rcpvWalld3 = vector.normalise([ (lcpvx[c] - rcpvx[c]) for c in range(3) ]) - rcpvWalld2 = vector.normalise(vector.crossproduct3(rcpvd1, rcpvWalld3)) - rcpvWalld1 = vector.crossproduct3(rcpvWalld2, rcpvWalld3) - lcpvWalld1 = [ -d for d in rcpvWalld1 ] - lcpvWalld2 = rcpvWalld2 - lcpvWalld3 = [ -d for d in rcpvWalld3 ] - cosLpvAngleUpRadians = math.cos(lpvAngleUpRadians) - sinLpvAngleUpRadians = math.sin(lpvAngleUpRadians) - lcpvWalld3 = [ (lcpvWalld3[c]*cosLpvAngleUpRadians + lcpvWalld2[c]*sinLpvAngleUpRadians) for c in range(3) ] - lcpvWalld2 = vector.crossproduct3(lcpvWalld3, lcpvWalld1) - cosRpvAngleUpRadians = math.cos(rpvAngleUpRadians) - sinRpvAngleUpRadians = math.sin(rpvAngleUpRadians) - rcpvWalld3 = [ (rcpvWalld3[c]*cosRpvAngleUpRadians + rcpvWalld2[c]*sinRpvAngleUpRadians) for c in range(3) ] - rcpvWalld2 = vector.crossproduct3(rcpvWalld3, rcpvWalld1) - - lipvCentred1 = vector.setMagnitude(lcpvWalld1, lpvInnerRadius) - lipvCentred2 = vector.setMagnitude(lcpvWalld2, lpvInnerRadius) - lipvCentred3 = vector.setMagnitude(lcpvWalld3, 2.0*lpvLengthFactor*lpvInnerRadius) - lipvCentrex = [ (lcpvx[c] + lpvSpacingFactor*lipvCentred1[c] - lipvCentred3[c]) for c in range(3) ] - lipvCentred3 = [ pvDerivativeFactor*d for d in lipvCentred3 ] - - lspvCentred1 = vector.setMagnitude(lcpvWalld1, lpvInnerRadius) - lspvCentred2 = vector.setMagnitude(lcpvWalld2, lpvInnerRadius) - lspvCentred3 = vector.setMagnitude(lcpvWalld3, 2.0*lpvLengthFactor*lpvInnerRadius) - lspvCentrex = [ (lcpvx[c] - lpvSpacingFactor*lspvCentred1[c] - lspvCentred3[c]) for c in range(3) ] - lspvCentred3 = [ pvDerivativeFactor*d for d in lspvCentred3 ] - - ripvCentred1 = vector.setMagnitude(rcpvWalld1, rpvInnerRadius) - ripvCentred2 = vector.setMagnitude(rcpvWalld2, rpvInnerRadius) - ripvCentred3 = vector.setMagnitude(rcpvWalld3, 2.0*rpvLengthFactor*rpvInnerRadius) - ripvCentrex = [ (rcpvx[c] - rpvSpacingFactor*ripvCentred1[c] - ripvCentred3[c]) for c in range(3) ] - ripvCentred3 = [ pvDerivativeFactor*d for d in ripvCentred3 ] - - rspvCentred1 = vector.setMagnitude(rcpvWalld1, rpvInnerRadius) - rspvCentred2 = vector.setMagnitude(rcpvWalld2, rpvInnerRadius) - rspvCentred3 = vector.setMagnitude(rcpvWalld3, 2.0*rpvLengthFactor*rpvInnerRadius) - rspvCentrex = [ (rcpvx[c] + rpvSpacingFactor*rspvCentred1[c] - rspvCentred3[c]) for c in range(3) ] - rspvCentred3 = [ pvDerivativeFactor*d for d in rspvCentred3 ] + radiansAround0, radiansAround1, radiansAround2 = radiansAround1, radiansAround2, radiansAround3 - if False: - node = nodes.createNode(nodeIdentifier, nodetemplate) - cache.setNode(node) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, lipvCentrex) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, lipvCentred1) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, lipvCentred2) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, lipvCentred3) - nodeIdentifier += 1 - - node = nodes.createNode(nodeIdentifier, nodetemplate) - cache.setNode(node) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, lspvCentrex) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, lspvCentred1) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, lspvCentred2) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, lspvCentred3) - nodeIdentifier += 1 - - node = nodes.createNode(nodeIdentifier, nodetemplate) - cache.setNode(node) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, ripvCentrex) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, ripvCentred1) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, ripvCentred2) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, ripvCentred3) - nodeIdentifier += 1 + # create atrial septum elements around fossa + meshGroups = [ laMeshGroup, raMeshGroup, aSeptumMeshGroup ] + for e1 in range(elementsCountAroundFossa): + e1p = (e1 + 1)%elementsCountAroundFossa + nids = [ asNodeId[0][e1], asNodeId[0][e1p], foNodeId[0][e1], foNodeId[0][e1p], \ + asNodeId[1][e1], asNodeId[1][e1p], foNodeId[1][e1], foNodeId[1][e1p] ] + eft1 = tricubichermite.createEftNoCrossDerivatives() + setEftScaleFactorIds(eft1, [1], []) + scalefactors = [ -1.0 ] + if e1 < elementsCountAroundAtrialSeptum: + scaleEftNodeValueLabels(eft1, [ 5, 6 ], [ Node.VALUE_LABEL_D_DS1 ], [ 1 ]) + scaleEftNodeValueLabels(eft1, [ 5, 6 ], [ Node.VALUE_LABEL_D_DS3 ], [ 1 ]) + if e1 == 0: + remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) + remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) + elif e1 == (elementsCountAroundAtrialSeptum - 1): + remapEftNodeValueLabel(eft1, [ 2 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) + remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) + else: + remapEftNodeValueLabel(eft1, [ 1, 2 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D2_DS1DS2, [] ) ]) # use temporary to swap later + remapEftNodeValueLabel(eft1, [ 5, 6 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D2_DS1DS2, [] ) ]) # use temporary to swap later + remapEftNodeValueLabel(eft1, [ 1, 2, 5, 6 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [] ) ]) + remapEftNodeValueLabel(eft1, [ 5, 6 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS3, [1] ) ]) + if e1 == elementsCountAroundAtrialSeptum: + remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D2_DS1DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) + remapEftNodeValueLabel(eft1, [ 2 ], Node.VALUE_LABEL_D2_DS1DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ) ]) + remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D2_DS1DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) + remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D2_DS1DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ) ]) + elif e1 == (elementsCountAroundFossa - 1): + remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D2_DS1DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ) ]) + remapEftNodeValueLabel(eft1, [ 2 ], Node.VALUE_LABEL_D2_DS1DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) + remapEftNodeValueLabel(eft1, [ 5 ], Node.VALUE_LABEL_D2_DS1DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ) ]) + remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D2_DS1DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) + remapEftNodeValueLabel(eft1, [ 2, 6 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS2, [1] ) ]) # set again to negate + else: + remapEftNodeValueLabel(eft1, [ 1, 2 ], Node.VALUE_LABEL_D2_DS1DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ) ]) + remapEftNodeValueLabel(eft1, [ 5, 6 ], Node.VALUE_LABEL_D2_DS1DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ) ]) + elementtemplateX.defineField(coordinates, -1, eft1) + element = mesh.createElement(elementIdentifier, elementtemplateX) + result2 = element.setNodesByIdentifier(eft1, nids) + if eft1.getNumberOfLocalScaleFactors() == 1: + result3 = element.setScaleFactors(eft1, [ -1.0 ]) + #print('create element as', element.isValid(), elementIdentifier, result2, result3, nids) + elementIdentifier += 1 + for meshGroup in meshGroups: + meshGroup.addElement(element) - node = nodes.createNode(nodeIdentifier, nodetemplate) - cache.setNode(node) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, rspvCentrex) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, rspvCentred1) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, rspvCentred2) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, rspvCentred3) - nodeIdentifier += 1 + # create elements around septum arch collapsed on interatrial groove + + eftAGroove = tricubichermite.createEftNoCrossDerivatives() + setEftScaleFactorIds(eftAGroove, [1], []) + scaleEftNodeValueLabels(eftAGroove, [ 7, 8 ], [ Node.VALUE_LABEL_D_DS3 ], [ 1 ]) + remapEftNodeValueLabel(eftAGroove, [ 1, 2, 7, 8 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) + remapEftNodeValueLabel(eftAGroove, [ 3, 4, 5, 6 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) + remapEftNodeValueLabel(eftAGroove, [ 1, 2, 5, 6 ], Node.VALUE_LABEL_D_DS3, []) + remapEftNodeValueLabel(eftAGroove, [ 1, 2, 3, 4, 5, 6, 7, 8 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [] ) ]) + ln_map = [ 1, 2, 3, 4, 1, 2, 5, 6 ] + remapEftLocalNodes(eftAGroove, 6, ln_map) + meshGroups = [ laMeshGroup, raMeshGroup ] + elementsCountOverArch = elementsCountOverAtria - 2 + elementtemplateX.defineField(coordinates, -1, eftAGroove) + scalefactors = [ -1.0 ] + for e1 in range(elementsCountOverArch): + ns = e1 + elementsCountAroundAtrialSeptum - elementsCountAroundFossa + nids = [ agNodeId[e1 + 1], agNodeId[e1 + 2], asNodeId[0][ns], asNodeId[0][ns + 1], asNodeId[1][ns], asNodeId[1][ns + 1] ] + eft1 = eftAGroove + if e1 == (elementsCountOverArch - 1): + eft1 = tricubichermite.createEftNoCrossDerivatives() + setEftScaleFactorIds(eft1, [1], []) + scaleEftNodeValueLabels(eft1, [ 7, 8 ], [ Node.VALUE_LABEL_D_DS3 ], [ 1 ]) + remapEftNodeValueLabel(eft1, [ 1, 4, 6, 7 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) + remapEftNodeValueLabel(eft1, [ 2, 3, 5, 8 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) + remapEftNodeValueLabel(eft1, [ 1, 2, 5, 6 ], Node.VALUE_LABEL_D_DS3, []) + remapEftNodeValueLabel(eft1, [ 1, 3, 5, 7 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [] ) ]) + remapEftNodeValueLabel(eft1, [ 2, 4, 6, 8 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [1] ) ]) + ln_map = [ 1, 2, 3, 4, 1, 2, 5, 6 ] + remapEftLocalNodes(eft1, 6, ln_map) + elementtemplateX.defineField(coordinates, -1, eft1) + element = mesh.createElement(elementIdentifier, elementtemplateX) + result2 = element.setNodesByIdentifier(eft1, nids) + result3 = element.setScaleFactors(eft1, scalefactors) + #print('create element ag', element.isValid(), elementIdentifier, result2, result3, nids) + elementIdentifier += 1 + for meshGroup in meshGroups: + meshGroup.addElement(element) - # create left and right pulmonary veins - pvCount = (lpvCount + rpvCount) if addInlets else 0 - - for i in range(pvCount): - if i < lpvCount: - elementsCountAcrossInlet = elementsCountAcrossLPV - elementsCountUpInlet = elementsCountUpLPV - elementsCountAroundInlet = elementsCountAroundLPV - inletInnerRadius = lpvInnerRadius - inletWallThickness = lpvWallThickness - if lpvCount == 1: - inletCentrex = lipvCentrex - inletCentred1 = lipvCentred1 - inletCentred2 = lipvCentred2 - inletCentred3 = lipvCentred3 - e1min = lipve1min - e2min = lipve2min - startRadians = 0.5*elementsCountUpInlet*math.pi/elementsCountAroundInlet - inletMeshGroups = [ lipvMeshGroup, lspvMeshGroup ] + # create left pulmonary vein annulus + lpvax = [ [ None ]*elementsCountAroundLpvOstium, [ None ]*elementsCountAroundLpvOstium ] + lpvad1 = [ [ None ]*elementsCountAroundLpvOstium, [ None ]*elementsCountAroundLpvOstium ] + lpvad2 = [ [ None ]*elementsCountAroundLpvOstium, [ None ]*elementsCountAroundLpvOstium ] + lpvad3 = [ [ None ]*elementsCountAroundLpvOstium, [ None ]*elementsCountAroundLpvOstium ] + lpvaNodeId = [ [ None ]*elementsCountAroundLpvOstium, [ None ]*elementsCountAroundLpvOstium ] + lpvaDerivativesMap = [ [ None ]*elementsCountAroundLpvOstium, [ None ]*elementsCountAroundLpvOstium ] + if commonLeftRightPvOstium: + # set points clockwise from interatrial groove at anterior venous limit + # insert at indexes such that 0 is the midpoint on interatrial groove + ix = interp.getNearestPointIndex(lpvox[1], agx[agn1Mid]) - elementsCountOverLeftAtriumVenous//2 + if ix > 0: + ix -= elementsCountAroundLpvOstium + # down interatrial groove from anterior venous limit, including both corners + for n1 in range(elementsCountOverLeftAtriumVenous + 1): + ns = n1 - elementsCountOverLeftAtriumVenous - 1 + ng = elementsCountOverLeftAtriumNonVenousAnterior + n1 + lpvax [0][ix] = asx [0][ns] + lpvad1[0][ix] = asd1[0][ns] + lpvad2[0][ix] = asd2[0][ns] + lpvad3[0][ix] = asd3[0][ns] + lpvaNodeId[0][ix] = asNodeId[0][ns] + lpvax [1][ix] = agx [ng] + lpvad1[1][ix] = agd1[ng] + lpvad2[1][ix] = agd2[ng] + lpvad3[1][ix] = agd3[ng] + lpvaNodeId[1][ix] = agNodeId[ng] + if n1 == 0: + lpvaDerivativesMap[0][ix] = ( (-1, 0, 0), (-1, -1, 0), (1, 0, 1), (0, 1, 0 ) ) + lpvaDerivativesMap[1][ix] = ( (-1, 0, 0), (-1, -1, 0), (-1, 0, 1), (0, 1, 0 ) ) + elif n1 == elementsCountOverLeftAtriumVenous: + lpvaDerivativesMap[0][ix] = ( (0, 1, 0), (-1, 1, 0), (1, 0, 1), (1, 0, 0 ) ) + lpvaDerivativesMap[1][ix] = ( (0, 1, 0), (-1, 1, 0), (-1, 0, 1), (1, 0, 0 ) ) else: - if i == (lpvCount - 2): # lipv - inletCentrex = lipvCentrex - inletCentred1 = lipvCentred1 - inletCentred2 = lipvCentred2 - inletCentred3 = lipvCentred3 - e1min = lipve1min - e2min = lipve2min - startRadians = 0.5*elementsCountUpInlet*math.pi/elementsCountAroundInlet - inletMeshGroups = [ lipvMeshGroup ] - else: # lspv or single lpv - inletCentrex = lspvCentrex - inletCentred1 = lspvCentred1 - inletCentred2 = lspvCentred2 - inletCentred3 = lspvCentred3 - e1min = lspve1min - e2min = lspve2min - startRadians = 0.5*elementsCountUpInlet*math.pi/elementsCountAroundInlet - inletMeshGroups = [ lspvMeshGroup ] - else: # rpv - elementsCountAcrossInlet = elementsCountAcrossRPV - elementsCountUpInlet = elementsCountUpRPV - elementsCountAroundInlet = elementsCountAroundRPV - inletInnerRadius = rpvInnerRadius - inletWallThickness = rpvWallThickness - if rpvCount == 1: - inletCentrex = ripvCentrex - inletCentred1 = ripvCentred1 - inletCentred2 = ripvCentred2 - inletCentred3 = ripvCentred3 - e1min = ripve1min - e2min = ripve2min - startRadians = (2*elementsCountUpInlet + elementsCountAcrossInlet)*math.pi/elementsCountAroundInlet - inletMeshGroup = [ ripvMeshGroup, rspvMeshGroup ] + lpvaDerivativesMap[0][ix] = ( (0, 1, 0), (-1, 0, 0), (1, 0, 1) ) + lpvaDerivativesMap[1][ix] = ( (0, 1, 0), (-1, 0, 0), (-1, 0, 1) ) + ix += 1 + # left around posterior venous limit lavq + for n1 in range(1, elementsCountAroundLeftAtriumRPV + elementsCountAroundLeftAtriumLPV): + nc = n1 + for n3 in range(2): + lpvax [n3][ix] = lavqx [n3][nc] + lpvad1[n3][ix] = lavqd1[n3][nc] + lpvad2[n3][ix] = lavqd2[n3][nc] + lpvad3[n3][ix] = lavqd3[n3][nc] + lpvaNodeId[n3][ix] = lavqNodeId[n3][nc] + lpvaDerivativesMap[n3][ix] = ( None, None, None ) + ix += 1 + # up left atrium over appendage laoa to interatrial groove + for n1 in range(1, elementsCountAroundLeftAtriumRPV + elementsCountOverSideLeftAtriumLPV): + no = -1 - n1 + for n3 in range(2): + lpvax [n3][ix] = laoax [n3][no] + lpvad1[n3][ix] = laoad1[n3][no] + lpvad2[n3][ix] = laoad2[n3][no] + lpvad3[n3][ix] = laoad3[n3][no] + lpvaNodeId[n3][ix] = laoaNodeId[n3][no] + if n1 == 1: + lpvaDerivativesMap[n3][ix] = ( (0, -1, 0), (1, -1, 0), None, (-1, 0, 0 ) ) + else: + lpvaDerivativesMap[n3][ix] = ( (-1, 0, 0), (0, -1, 0), None ) + ix += 1 + else: + # set points clockwise from venous midpoint at anterior venous limit + # insert at indexes such that 0 is one past the midpoint on venous midline + ix = -elementsCountOverLeftAtriumVenous//2 + # down left atrium venous midpoint line + for n1 in range(elementsCountOverLeftAtriumVenous + 1): + for n3 in range(2): + lpvax [n3][ix] = lamlx [n3][n1] + lpvad1[n3][ix] = lamld1[n3][n1] + lpvad2[n3][ix] = lamld2[n3][n1] + lpvad3[n3][ix] = lamld3[n3][n1] + lpvaNodeId[n3][ix] = lamlNodeId[n3][n1] + if n1 == 0: + lpvaDerivativesMap[n3][ix] = ( (0, 1, 0), (-1, 0, 0), None, (1, 1, 0 ) ) + elif n1 == elementsCountOverLeftAtriumVenous: + lpvaDerivativesMap[n3][ix] = ( (-1, 1, 0), (-1, 0, 0), None, (0, 1, 0 ) ) + else: + lpvaDerivativesMap[n3][ix] = ( (0, 1, 0), (-1, 0, 0), None ) + ix += 1 + # left around left cs, including 2 corners + for n1 in range(elementsCountAroundLeftAtriumLPV + 1): + nc = lan1MidVenous - n1 + for n3 in range(2): + lpvax [n3][ix] = lavtx [n3][nc] + lpvad1[n3][ix] = lavtd1[n3][nc] + lpvad2[n3][ix] = lavtd2[n3][nc] + lpvad3[n3][ix] = lavtd3[n3][nc] + lpvaNodeId[n3][ix] = lavtNodeId[n3][nc] + if n1 == 0: + lpvaDerivativesMap[n3][ix] = ( (0, -1, 0), (1, -1, 0), None, (-1, 0, 0 ) ) + elif n1 == elementsCountAroundLeftAtriumLPV: + lpvaDerivativesMap[n3][ix] = ( (-1, 0, 0), (-1, -1, 0), None, (0, 1, 0 ) ) + else: + lpvaDerivativesMap[n3][ix] = ( (-1, 0, 0), (0, -1, 0), None ) + ix += 1 + # up left atrium venous side line + for n1 in range(elementsCountOverSideLeftAtriumLPV): + no = -2 - n1 + for n3 in range(2): + lpvax [n3][ix] = laoax [n3][no] + lpvad1[n3][ix] = laoad1[n3][no] + lpvad2[n3][ix] = laoad2[n3][no] + lpvad3[n3][ix] = laoad3[n3][no] + lpvaNodeId[n3][ix] = laoaNodeId[n3][no] + if n1 == (elementsCountOverSideLeftAtriumLPV - 1): + lpvaDerivativesMap[n3][ix] = ( (-1, 0, 0), (-1, -1, 0), None, (0, 1, 0) ) + else: + lpvaDerivativesMap[n3][ix] = ( (-1, 0, 0), (0, -1, 0), None ) + ix += 1 + #print('lpvaNodeId[0]',lpvaNodeId[0]) + #print('lpvaNodeId[1]',lpvaNodeId[1]) + #print('lpvaDerivativesMap[0]',lpvaDerivativesMap[0]) + #print('lpvaDerivativesMap[1]',lpvaDerivativesMap[1]) + nodeIdentifier, elementIdentifier = createAnnulusMesh3d( + nodes, mesh, nodeIdentifier, elementIdentifier, + lpvox, lpvod1, lpvod2, lpvod3, lpvoNodeId, None, + lpvax, lpvad1, lpvad2, lpvad3, lpvaNodeId, lpvaDerivativesMap, + elementsCountRadial = 1, meshGroups = [ laMeshGroup ]) + + if not commonLeftRightPvOstium: + # create right pulmonary vein annulus + rpvax = [ [ None ]*elementsCountAroundRpvOstium, [ None ]*elementsCountAroundRpvOstium ] + rpvad1 = [ [ None ]*elementsCountAroundRpvOstium, [ None ]*elementsCountAroundRpvOstium ] + rpvad2 = [ [ None ]*elementsCountAroundRpvOstium, [ None ]*elementsCountAroundRpvOstium ] + rpvad3 = [ [ None ]*elementsCountAroundRpvOstium, [ None ]*elementsCountAroundRpvOstium ] + rpvaNodeId = [ [ None ]*elementsCountAroundRpvOstium, [ None ]*elementsCountAroundRpvOstium ] + rpvaDerivativesMap = [ [ None ]*elementsCountAroundRpvOstium, [ None ]*elementsCountAroundRpvOstium ] + # set points clockwise from interatrial groove at anterior venous limit + # insert at indexes such that 0 is the midpoint on interatrial groove + ix = -elementsCountOverLeftAtriumVenous//2 + # down interatrial groove from anterior venous limit, including both corners + for n1 in range(elementsCountOverLeftAtriumVenous + 1): + ns = n1 - elementsCountOverLeftAtriumVenous - 1 + ng = elementsCountOverLeftAtriumNonVenousAnterior + n1 + rpvax [0][ix] = asx [0][ns] + rpvad1[0][ix] = asd1[0][ns] + rpvad2[0][ix] = asd2[0][ns] + rpvad3[0][ix] = asd3[0][ns] + rpvaNodeId[0][ix] = asNodeId[0][ns] + rpvax [1][ix] = agx [ng] + rpvad1[1][ix] = agd1[ng] + rpvad2[1][ix] = agd2[ng] + rpvad3[1][ix] = agd3[ng] + rpvaNodeId[1][ix] = agNodeId[ng] + if n1 == 0: + rpvaDerivativesMap[0][ix] = ( (-1, 0, 0), (-1, -1, 0), (1, 0, 1), (0, 1, 0 ) ) + rpvaDerivativesMap[1][ix] = ( (-1, 0, 0), (-1, -1, 0), (-1, 0, 1), (0, 1, 0 ) ) + elif n1 == elementsCountOverLeftAtriumVenous: + rpvaDerivativesMap[0][ix] = ( (0, 1, 0), (-1, 1, 0), (1, 0, 1), (1, 0, 0 ) ) + rpvaDerivativesMap[1][ix] = ( (0, 1, 0), (-1, 1, 0), (-1, 0, 1), (1, 0, 0 ) ) else: - if i == (lpvCount + rpvCount - 2): # ripv; unused if single rpv - inletCentrex = ripvCentrex - inletCentred1 = ripvCentred1 - inletCentred2 = ripvCentred2 - inletCentred3 = ripvCentred3 - e1min = ripve1min - e2min = ripve2min - startRadians = (2*elementsCountUpInlet + elementsCountAcrossInlet)*math.pi/elementsCountAroundInlet - inletMeshGroups = [ ripvMeshGroup ] - else: # rspv or single rpv - inletCentrex = rspvCentrex - inletCentred1 = rspvCentred1 - inletCentred2 = rspvCentred2 - inletCentred3 = rspvCentred3 - e1min = rspve1min - e2min = rspve2min - startRadians = -0.75*elementsCountUpInlet*math.pi/elementsCountAroundInlet - inletMeshGroups = [ rspvMeshGroup ] - - inletStartInnerx, inletStartInnerd1 = createCirclePoints(inletCentrex, inletCentred1, inletCentred2, elementsCountAroundInlet, startRadians) - inletStartOuterx, inletStartOuterd1 = createCirclePoints(inletCentrex, vector.setMagnitude(inletCentred1, inletInnerRadius + inletWallThickness), \ - vector.setMagnitude(inletCentred2, inletInnerRadius + inletWallThickness), elementsCountAroundInlet, startRadians) - inletStartx = [ inletStartInnerx , inletStartOuterx ] - inletStartd1 = [ inletStartInnerd1, inletStartOuterd1 ] - inletStartd2 = [ [ inletCentred3 ]*elementsCountAroundInlet ]*2 - - inletEndx = [ [], [] ] - inletEndd1 = [ [], [] ] - inletEndd2 = [ [], [] ] - inletEndd3 = [ [], [] ] - inletEndNodeId = [ [], [] ] - inletEndDerivativesMap = [ [], [] ] - n1min = e1min - n1max = e1min + elementsCountAcrossInlet - n2min = e2min - n2max = e2min + elementsCountUpInlet + rpvaDerivativesMap[0][ix] = ( (0, 1, 0), (-1, 0, 0), (1, 0, 1) ) + rpvaDerivativesMap[1][ix] = ( (0, 1, 0), (-1, 0, 0), (-1, 0, 1) ) + ix += 1 + # left over posterior venous limit + for n1 in range(1, elementsCountAroundLeftAtriumRPV): + for n3 in range(2): + rpvax [n3][ix] = lavqx [n3][n1] + rpvad1[n3][ix] = lavqd1[n3][n1] + rpvad2[n3][ix] = lavqd2[n3][n1] + rpvad3[n3][ix] = lavqd3[n3][n1] + rpvaNodeId[n3][ix] = lavqNodeId[n3][n1] + rpvaDerivativesMap[n3][ix] = ( None, None, None ) + ix += 1 + # up left atrium venous midline, including both corners + for n1 in range(elementsCountOverLeftAtriumVenous + 1): + nm = elementsCountOverLeftAtriumVenous - n1 + for n3 in range(2): + rpvax [n3][ix] = lamlx [n3][nm] + rpvad1[n3][ix] = lamld1[n3][nm] + rpvad2[n3][ix] = lamld2[n3][nm] + rpvad3[n3][ix] = lamld3[n3][nm] + rpvaNodeId[n3][ix] = lamlNodeId[n3][nm] + if n1 == 0: + rpvaDerivativesMap[n3][ix] = ( (1, 0, 0), (0, 1, 0), None, (1, -1, 0) ) + elif n1 == elementsCountOverLeftAtriumVenous: + rpvaDerivativesMap[n3][ix] = ( (-1, -1, 0), (0, -1, 0), None, (-1, 0, 0) ) + else: + rpvaDerivativesMap[n3][ix] = ( (0, -1, 0), (1, 0, 0), None ) + ix += 1 + # right over anterior venous limit + for n1 in range(1, elementsCountAroundLeftAtriumRPV): + na = elementsCountAroundLeftAtriumRPV - n1 + for n3 in range(2): + rpvax [n3][ix] = lavbx [n3][na] + rpvad1[n3][ix] = lavbd1[n3][na] + rpvad2[n3][ix] = lavbd2[n3][na] + rpvad3[n3][ix] = lavbd3[n3][na] + rpvaNodeId[n3][ix] = lavbNodeId[n3][na] + rpvaDerivativesMap[n3][ix] = ( (-1, 0, 0), (0, -1, 0), None ) + ix += 1 + #print('rpvaNodeId[0]',rpvaNodeId[0]) + #print('rpvaNodeId[1]',rpvaNodeId[1]) + #print('rpvaDerivativesMap[0]',rpvaDerivativesMap[0]) + #print('rpvaDerivativesMap[1]',rpvaDerivativesMap[1]) + nodeIdentifier, elementIdentifier = createAnnulusMesh3d( + nodes, mesh, nodeIdentifier, elementIdentifier, + rpvox, rpvod1, rpvod2, rpvod3, rpvoNodeId, None, + rpvax, rpvad1, rpvad2, rpvad3, rpvaNodeId, rpvaDerivativesMap, + elementsCountRadial = 1, meshGroups = [ laMeshGroup ]) + + # create inferior and superior vena cavae inlets + elementsCountAlongVCInlet = 2 # GRC make into a setting? + for v in range(2): + if v == 0: + proportion1 = 1.0 - ivcPositionOver + proportion2 = ivcPositionRight + vcAngle1Radians = -ivcAngleOverRadians + vcAngle2Radians = -ivcAngleLeftRadians + vcDerivativeFactor = ivcDerivativeFactor + vcLength = ivcLength + vcInnerRadius = ivcInnerRadius + vcWallThickness = ivcWallThickness + elementsCountAroundVC = elementsCountAroundRightAtriumPosteriorVenous + elementsCountOverSideRightAtriumVC + elementsCountOverRightAtriumVenous + startRadians = math.pi*elementsCountAroundRightAtriumPosteriorVenous/elementsCountAroundVC + else: + proportion1 = 1.0 - svcPositionOver + proportion2 = svcPositionRight + vcAngle1Radians = -svcAngleOverRadians + vcAngle2Radians = -svcAngleLeftRadians + vcDerivativeFactor = svcDerivativeFactor + vcLength = svcLength + vcInnerRadius = svcInnerRadius + vcWallThickness = svcWallThickness + elementsCountAroundVC = elementsCountOverRightAtriumVenous//2 + elementsCountOverCristaTerminalisAnterior + elementsCountOverSideRightAtriumVC + startRadians = math.pi*elementsCountOverSideRightAtriumVC/elementsCountAroundVC + # vessel + vcOuterRadius = vcInnerRadius + vcWallThickness + vcEndDerivative = vcDerivativeFactor*vcLength/elementsCountAlongVCInlet + ocxPosition = raTrackSurface.createPositionProportion(proportion1, proportion2) + ocx, d1, d2 = raTrackSurface.evaluateCoordinates(ocxPosition, derivatives = True) + ocd1, ocd2, ocd3 = calculate_surface_axes(d1, d2, vector.normalise(d1)) + vcx, vd1, vd2, vd3 = getCircleProjectionAxes(ocx, ocd1, ocd2, ocd3, vcLength, vcAngle1Radians, vcAngle2Radians) + vcd1 = vd1 + vcd2 = [ -d for d in vd2 ] + vcd3 = [ -vcEndDerivative*d for d in vd3 ] + vcvx = [ None, None ] + vcvd1 = [ None, None ] + vcvd2 = [ None, None ] for n3 in range(2): - if n3 == 0: - lax = laInnerx - lad1 = laInnerd1 - lad2 = laInnerd2 - lad3 = laInnerd3 - else: - lax = laOuterx - lad1 = laOuterd1 - lad2 = laOuterd2 - lad3 = laOuterd3 - for n in range(elementsCountAroundInlet): - if n < elementsCountAcrossInlet: - # left - derivativesMap = [ ( 0, -1, 0 ), ( 1, -1, 0 ), None, ( -1, 0, 0 ) ] if (n == 0) else [ ( -1, 0, 0 ), ( 0, -1, 0 ), None ] - n1 = n1max - n - n2 = n2min - elif n < (elementsCountAcrossInlet + elementsCountUpInlet): - # up - derivativesMap = [ ( -1, 0, 0 ), ( -1, -1, 0 ), None, ( 0, 1, 0 ) ] if (n == elementsCountAcrossInlet) else [ ( 0, 1, 0 ), ( -1, 0, 0 ), None ] - n1 = n1min - n2 = n2min + (n - elementsCountAcrossInlet) - elif i < lpvCount: - # lpv apex and down - if n == (elementsCountAcrossInlet + elementsCountUpInlet): - if lpvCount == 1: - derivativesMap = [ ( 0, 1, 0 ), ( -1, 0, 0 ), None ] - else: # if lpvCount == 2: - if i == 0: - derivativesMap = [ ( -1, 0, 0 ), ( -1, -1, 0 ), None, ( 0, 1, 0 ) ] - else: - derivativesMap = [ ( 0, 1, 0 ), ( -1, 1, 0 ), None, ( 1, 0, 0 ) ] - n1 = elementsCountRidgeVenous + radius = vcInnerRadius if (n3 == 0) else vcOuterRadius + px, pd1 = createCirclePoints(vcx, vector.setMagnitude(vcd1, radius), vector.setMagnitude(vcd2, radius), elementsCountAroundVC, startRadians) + vcvx [n3] = px + vcvd1[n3] = pd1 + vcvd2[n3] = [ vcd3 ]*elementsCountAroundVC + # annulus/aperture + vcax = [ [ None ]*elementsCountAroundVC, [ None ]*elementsCountAroundVC ] + vcad1 = [ [ None ]*elementsCountAroundVC, [ None ]*elementsCountAroundVC ] + vcad2 = [ [ None ]*elementsCountAroundVC, [ None ]*elementsCountAroundVC ] + vcad3 = [ [ None ]*elementsCountAroundVC, [ None ]*elementsCountAroundVC ] + vcaNodeId = [ [ None ]*elementsCountAroundVC, [ None ]*elementsCountAroundVC ] + vcaDerivativesMap = [ [ None ]*elementsCountAroundVC, [ None ]*elementsCountAroundVC ] + if v == 0: # ivc + # set points clockwise from interatrial groove at venous midpoint + ix = 0 + # over interatrial groove from cristvenous midpoint to anterior, including both corners + for n1 in range(elementsCountOverRightAtriumVenous//2, -1, -1): + ns = (elementsCountAroundAtrialSeptum - 1 + elementsCountOverRightAtriumNonVenousAnterior + elementsCountOverRightAtriumVenous//2 + n1) % elementsCountAroundFossa + ng = elementsCountOverRightAtriumNonVenousAnterior + elementsCountOverRightAtriumVenous//2 + n1 + #print('v',v,'ix', ix, 'n1', n1, 'ns', ns, 'ng', ng) + vcax [0][ix] = asx [1][ns] + vcad1[0][ix] = asd1[1][ns] + vcad2[0][ix] = asd2[1][ns] + vcad3[0][ix] = asd3[1][ns] + vcaNodeId[0][ix] = asNodeId[1][ns] + vcax [1][ix] = agx [ng] + vcad1[1][ix] = agd1[ng] + vcad2[1][ix] = agd2[ng] + vcad3[1][ix] = agd3[ng] + vcaNodeId[1][ix] = agNodeId[ng] + if n1 == (elementsCountOverRightAtriumVenous//2): + # on vestibule top, d1 and d2 are reversed + vcaDerivativesMap[0][ix] = ( (-1, 0, 0), (-1, -1, 0), (1, 0, 1), (0, 1, 0 ) ) + vcaDerivativesMap[1][ix] = ( (-1, 0, 0), (-1, -1, 0), (-1, 0, 1), (0, 1, 0 ) ) + elif n1 == 0: + vcaDerivativesMap[0][ix] = ( (0, -1, 0), (1, -1, 0), (-1, 0, 1), (-1, 0, 0 ) ) + vcaDerivativesMap[1][ix] = ( (0, -1, 0), (1, -1, 0), (1, 0, 1), (-1, 0, 0 ) ) + else: + vcaDerivativesMap[0][ix] = ( (0, -1, 0), (1, 0, 0), (-1, 0, 1) ) + vcaDerivativesMap[1][ix] = ( (0, -1, 0), (1, 0, 0), (1, 0, 1) ) + ix += 1 + # right over venous midline to crista terminalis, excluding corners + for n1 in range(1, elementsCountOverSideRightAtriumVC): + nm = elementsCountOverSideRightAtriumVC - n1 + #print('v',v,'ix', ix, 'n1', n1, 'nm', nm) + for n3 in range(2): + vcax [n3][ix] = ravmx [n3][nm] + vcad1[n3][ix] = ravmd1[n3][nm] + vcad2[n3][ix] = ravmd2[n3][nm] + vcad3[n3][ix] = ravmd3[n3][nm] + vcaNodeId[n3][ix] = ravmNodeId[n3][nm] + vcaDerivativesMap[n3][ix] = ( (-1, 0, 0), (0, -1, 0), None ) + ix += 1 + # back over crista terminalis to vestibule top including corners + for n1 in range(elementsCountOverCristaTerminalisPosterior + 1): + nc = elementsCountOverCristaTerminalisAnterior + n1 + #print('v',v,'ix', ix, 'n1', n1, 'nc', nc) + for n3 in range(2): + vcax [n3][ix] = ractx [n3][nc] + vcad1[n3][ix] = ractd1[n3][nc] + vcad2[n3][ix] = ractd2[n3][nc] + vcad3[n3][ix] = ractd3[n3][nc] + vcaNodeId[n3][ix] = ractNodeId[n3][nc] + if n1 == 0: + vcaDerivativesMap[n3][ix] = ( (-1, 0, 0), (-1, -1, 0), None, (0, 1, 0) ) + elif n1 == elementsCountOverCristaTerminalisPosterior: + # on vestibule top, d1 and d2 are reversed + vcaDerivativesMap[n3][ix] = ( (0, -1, 0), (1, -1, 0), None, (-1, 0, 0) ) else: - derivativesMap = [ ( 0, -1, 0 ), ( 1, 0, 0 ), None ] - n1 = n1max - n2 = n2max - (n - (elementsCountAcrossInlet + elementsCountUpInlet)) + vcaDerivativesMap[n3][ix] = ( (0, 1, 0), (-1, 0, 0), None ) + ix += 1 + # left around vestibule top on venous posterior + for n1 in range(1, elementsCountAroundRightAtriumPosteriorVenous): + nc = elementsCountAroundRightAtriumPosteriorVenous - n1 + #print('v',v,'ix', ix, 'n1', n1, 'nc', nc) + for n3 in range(2): + vcax [n3][ix] = ravtx [n3][nc] + vcad1[n3][ix] = ravtd1[n3][nc] + vcad2[n3][ix] = ravtd2[n3][nc] + vcad3[n3][ix] = ravtd3[n3][nc] + vcaNodeId[n3][ix] = ravtNodeId[n3][nc] + vcaDerivativesMap[n3][ix] = ( (-1, 0, 0), (0, -1, 0), None ) + ix += 1 + else: # svc + # set points clockwise from interatrial groove at venous limit posterior + ix = 0 + # over interatrial groove from venous midpoint to anterior, including both corners + for n1 in range(elementsCountOverRightAtriumVenous//2, -1, -1): + ns = elementsCountAroundAtrialSeptum - 1 + elementsCountOverRightAtriumNonVenousAnterior + n1 + ng = elementsCountOverRightAtriumNonVenousAnterior + n1 + #print('v',v,'ix', ix, 'n1', n1, 'ns', ns, 'ng', ng) + vcax [0][ix] = asx [1][ns] + vcad1[0][ix] = asd1[1][ns] + vcad2[0][ix] = asd2[1][ns] + vcad3[0][ix] = asd3[1][ns] + vcaNodeId[0][ix] = asNodeId[1][ns] + vcax [1][ix] = agx [ng] + vcad1[1][ix] = agd1[ng] + vcad2[1][ix] = agd2[ng] + vcad3[1][ix] = agd3[ng] + vcaNodeId[1][ix] = agNodeId[ng] + if n1 == (elementsCountOverRightAtriumVenous//2): + vcaDerivativesMap[0][ix] = ( (1, 0, 0), (1, 1, 0), (-1, 0, 1), (0, -1, 0 ) ) + vcaDerivativesMap[1][ix] = ( (1, 0, 0), (1, 1, 0), (1, 0, 1), (0, -1, 0 ) ) + elif n1 == 0: + vcaDerivativesMap[0][ix] = ( (0, -1, 0), (1, -1, 0), (-1, 0, 1), (-1, 0, 0 ) ) + vcaDerivativesMap[1][ix] = ( (0, -1, 0), (1, -1, 0), (1, 0, 1), (-1, 0, 0 ) ) else: - # rpv right and down - if n < (elementsCountAcrossInlet*2 + elementsCountUpInlet): - # rpv right - derivativesMap = [ ( 0, 1, 0 ), ( -1, 1, 0 ), None, ( 1, 0, 0 ) ] if (n == (elementsCountAcrossInlet + elementsCountUpInlet)) else [ None, None, None ] - n1 = n1min + (n - (elementsCountAcrossInlet + elementsCountUpInlet)) - n2 = n2max + vcaDerivativesMap[0][ix] = ( (0, -1, 0), (1, 0, 0), (-1, 0, 1) ) + vcaDerivativesMap[1][ix] = ( (0, -1, 0), (1, 0, 0), (1, 0, 1) ) + ix += 1 + # back over crista terminalis to vestibule top including corners + for n1 in range(1, elementsCountOverCristaTerminalisAnterior + 1): + nc = n1 + #print('v',v,'ix', ix, 'n1', n1, 'nc', nc) + for n3 in range(2): + vcax [n3][ix] = ractx [n3][nc] + vcad1[n3][ix] = ractd1[n3][nc] + vcad2[n3][ix] = ractd2[n3][nc] + vcad3[n3][ix] = ractd3[n3][nc] + vcaNodeId[n3][ix] = ractNodeId[n3][nc] + if n1 == 0: + vcaDerivativesMap[n3][ix] = ( (-1, 0, 0), (-1, -1, 0), None, (0, 1, 0) ) + elif n1 == elementsCountOverCristaTerminalisAnterior: + vcaDerivativesMap[n3][ix] = ( (0, 1, 0), (-1, 1, 0), None, (1, 0, 0) ) else: - # rpv down - derivativesMap = [ ( 1, 0, 0 ), ( 1, 1, 0 ), None, ( 0, -1, 0 ) ] if (n == (elementsCountAcrossInlet*2 + elementsCountUpInlet)) else [ ( 0, -1, 0 ), ( 1, 0, 0 ), None ] - n1 = n1max - n2 = n2max - (n - (elementsCountAcrossInlet*2 + elementsCountUpInlet)) - if i == lpvCount: - # ripv, or single rpv straddling ridge - if (n3 == 1) and (n1 == n1max): - # fix ripv derivative 3 mapping adjacent to collapsed nodes maps to ds1 + ds3 - derivativesMap[2] = ( 1, 0, 1 ) - if n2 >= elementsCountUpAtria: - # reverse derivatives 1 and 2 on ridge - derivativesMap[0] = ( -1, 0, 0) if (derivativesMap[0] is None) else ( -derivativesMap[0][0], -derivativesMap[0][1], derivativesMap[0][2]) - derivativesMap[1] = ( 0, -1, 0) if (derivativesMap[1] is None) else ( -derivativesMap[1][0], -derivativesMap[1][1], derivativesMap[1][2]) - derivativesMap[2] = None if (derivativesMap[2] is None) else ( -derivativesMap[2][0], -derivativesMap[2][1], derivativesMap[2][2]) - if len(derivativesMap) > 3: - derivativesMap[3] = ( -1, 0, 0) if (derivativesMap[3] is None) else ( -derivativesMap[3][0], -derivativesMap[3][1], derivativesMap[3][2]) - # mirror to get coordinates from anterior - n1 = elementsCountAroundAtrialFreeWall - n1 - n2 = elementsCountUpAtria*2 - n2 - elif (i == (lpvCount + 1)) and (n3 == 1) and (n1 == n1min): - # fix rspv derivative 3 mapping adjacent to collapsed nodes maps to -ds1 + ds3 - derivativesMap[2] = ( -1, 0, 1 ) - - # get derivatives 1 and 2 from node as some have been reduced on la beside inlets - cache.setNode(nodes.findNodeByIdentifier(laNodeId[n3][n2][n1])) - result, d1 = coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, 3) - result, d2 = coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, 3) - inletEndx [n3].append(lax [n2][n1]) - inletEndd1[n3].append(d1) - inletEndd2[n3].append(d2) - inletEndd3[n3].append(lad3[n2][n1]) - inletEndNodeId[n3].append(laNodeId[n3][n2][n1]) - inletEndDerivativesMap[n3].append(derivativesMap) - - nodeIdentifier, elementIdentifier = tricubichermite.createAnnulusMesh3d( - inletStartx, inletStartd1, inletStartd2, None, None, None, - inletEndx, inletEndd1, inletEndd2, inletEndd3, inletEndNodeId, inletEndDerivativesMap, - nodetemplate, nodetemplateLinearS3, nodeIdentifier, elementIdentifier, - elementsCountRadial = elementsCountInlet, maxEndThickness = 1.5*aFreeWallThickness, - meshGroups = [ laMeshGroup ] + inletMeshGroups) - - # create vena cavae inlets to right atrium - - # get vena cava inlet positions - # GRC fudgefactors, multiple of inner radius that inlet centre is away from septum - ivcSeptumDistanceFactor = 1.0 - svcSeptumDistanceFactor = 1.0 - vcDerivativeFactor = 1.0/elementsCountInlet # GRC fudge factor - raSeptumModX = -aBaseInnerMajorMag*math.cos(aMajorAxisRadians)*math.cos(-laSeptumRadians) \ - + aBaseInnerMinorMag*math.sin(aMajorAxisRadians)*math.sin(-laSeptumRadians) - raOuterMajorx = [ -aBaseOuterMajorMag*math.cos(aMajorAxisRadians), -aBaseOuterMajorMag*math.sin(aMajorAxisRadians), 0.0 ] - raOuterMinorx = [ aBaseOuterMinorMag*math.sin(aMajorAxisRadians), -aBaseOuterMinorMag*math.cos(aMajorAxisRadians), 0.0 ] - ivcdx = raSeptumModX + ivcSeptumDistanceFactor*ivcInnerRadius - ivcBaseRadians = getEllipseRadiansToX(raOuterMajorx[0], raOuterMinorx[0], ivcdx, -laSeptumRadians + 0.5*math.pi) - svcdx = raSeptumModX + svcSeptumDistanceFactor*svcInnerRadius - svcBaseRadians = getEllipseRadiansToX(raOuterMajorx[0], raOuterMinorx[0], svcdx, -laSeptumRadians - 0.5*math.pi) - - raCentre = [ -laCentre[0], laCentre[1], laCentre[2] ] - ax = [ raCentre[c] + raOuterMajorx[c]*math.cos(svcBaseRadians) + raOuterMinorx[c]*math.sin(svcBaseRadians) for c in range(3) ] - ad2 = [ 0.0, math.sin(aBaseFrontInclineRadians), math.cos(aBaseFrontInclineRadians) ] - bx = [ raCentre[c] + raOuterMajorx[c]*math.cos(ivcBaseRadians) + raOuterMinorx[c]*math.sin(ivcBaseRadians) for c in range(3) ] - bd2 = [ 0.0, math.sin(aBaseBackInclineRadians), -math.cos(aBaseBackInclineRadians) ] - # approximate middle point as half way between ax and bx at aOuterHeight - mx = [ 0.5*(ax[0] + bx[0]), 0.5*(ax[1] + bx[1]), aOuterHeight ] - md2 = [ 0.5*(bx[0] - ax[0]), 0.5*(bx[1] - ax[1]), 0.0 ] - - gap = 2.0 - svcPositionUp - ivcPositionUp - px = interp.sampleCubicHermiteCurves([ ax, mx, bx ], [ ad2, md2, bd2 ], elementsCountOut = 3, \ - lengthFractionStart = svcPositionUp/gap, lengthFractionEnd = ivcPositionUp/gap, arcLengthDerivatives = True)[0] - svcWallx = px[1] - ivcWallx = px[2] - - ivcWalld3 = vector.normalise([ (svcWallx[c] - ivcWallx[c]) for c in range(3) ]) - ivcWalld1 = vector.normalise([ ivcWalld3[1], -ivcWalld3[0], 0.0 ]) - ivcWalld2 = vector.crossproduct3(ivcWalld3, ivcWalld1) - ivcDistance = 2.0*ivcLengthFactor*ivcInnerRadius - - cosIvcAngleUpRadians = math.cos(ivcAngleUpRadians) - sinIvcAngleUpRadians = math.sin(ivcAngleUpRadians) - - ivcCentred1 = ivcWalld1 - ivcCentred2 = [ (ivcWalld2[c]*cosIvcAngleUpRadians - ivcWalld3[c]*sinIvcAngleUpRadians) for c in range(3) ] - ivcCentred3 = [ (ivcWalld3[c]*cosIvcAngleUpRadians + ivcWalld2[c]*sinIvcAngleUpRadians) for c in range(3) ] - - if math.fabs(ivcAngleLeftRadians) < 1.0E-6: - ivcCentrex = [ (ivcWallx[c] - ivcDistance*ivcCentred3[c]) for c in range(3) ] - else: - # rotate left is around a circular arc - ivcTempd1 = ivcCentred1 - ivcTempd2 = ivcCentred2 - ivcTempd3 = ivcCentred3 - cosIvcAngleLeftRadians = math.cos(ivcAngleLeftRadians) - sinIvcAngleLeftRadians = math.sin(ivcAngleLeftRadians) - ivcCentred1 = [ (ivcTempd1[c]*cosIvcAngleLeftRadians - ivcTempd3[c]*sinIvcAngleLeftRadians) for c in range(3) ] - ivcCentred2 = ivcTempd2 - ivcCentred3 = [ (ivcTempd3[c]*cosIvcAngleLeftRadians + ivcTempd1[c]*sinIvcAngleLeftRadians) for c in range(3) ] - r = ivcDistance/ivcAngleLeftRadians - offsetd1 = r*(1.0 - cosIvcAngleLeftRadians) - offsetd3 = -r*sinIvcAngleLeftRadians - ivcCentrex = [ (ivcWallx[c] + offsetd1*ivcCentred1[c] + offsetd3*ivcCentred3[c]) for c in range(3) ] - ivcCentred1 = [ ivcInnerRadius*d for d in ivcCentred1 ] - ivcCentred2 = [ ivcInnerRadius*d for d in ivcCentred2 ] - ivcCentred3 = [ ivcDistance*vcDerivativeFactor*d for d in ivcCentred3 ] - - svcWalld3 = vector.normalise([ (ivcWallx[c] - svcWallx[c]) for c in range(3) ]) - svcWalld1 = vector.normalise([ svcWalld3[1], -svcWalld3[0], 0.0 ]) - svcWalld2 = vector.crossproduct3(svcWalld3, svcWalld1) - svcDistance = 2.0*svcLengthFactor*svcInnerRadius - cosSvcAngleUpRadians = math.cos(svcAngleUpRadians) - sinSvcAngleUpRadians = math.sin(svcAngleUpRadians) - svcCentred1 = vector.setMagnitude(svcWalld1, svcInnerRadius) - svcCentred2 = [ svcInnerRadius*(svcWalld2[c]*cosSvcAngleUpRadians - svcWalld3[c]*sinSvcAngleUpRadians) for c in range(3) ] - svcCentred3 = [ svcDistance *(svcWalld3[c]*cosSvcAngleUpRadians + svcWalld2[c]*sinSvcAngleUpRadians) for c in range(3) ] - svcCentrex = [ (svcWallx[c] - svcCentred3[c]) for c in range(3) ] - svcCentred3 = [ vcDerivativeFactor*d for d in svcCentred3 ] - + vcaDerivativesMap[n3][ix] = ( (0, 1, 0), (-1, 0, 0), None ) + ix += 1 + # left over venous midline to interatrial groove, excluding corners + for n1 in range(1, elementsCountOverSideRightAtriumVC): + nm = n1 + #print('v',v,'ix', ix, 'n1', n1, 'nm', nm) + for n3 in range(2): + vcax [n3][ix] = ravmx [n3][nm] + vcad1[n3][ix] = ravmd1[n3][nm] + vcad2[n3][ix] = ravmd2[n3][nm] + vcad3[n3][ix] = ravmd3[n3][nm] + vcaNodeId[n3][ix] = ravmNodeId[n3][nm] + vcaDerivativesMap[n3][ix] = ( None, None, None ) + ix += 1 + #print('vcaNodeId[0]',vcaNodeId[0]) + #print('vcaNodeId[1]',vcaNodeId[1]) + #print('vcaDerivativesMap[0]',vcaDerivativesMap[0]) + #print('vcaDerivativesMap[1]',vcaDerivativesMap[1]) + nodeIdentifier, elementIdentifier = createAnnulusMesh3d( + nodes, mesh, nodeIdentifier, elementIdentifier, + vcvx, vcvd1, vcvd2, None, None, None, + vcax, vcad1, vcad2, vcad3, vcaNodeId, vcaDerivativesMap, + maxEndThickness = 1.5*raVenousFreeWallThickness, + elementsCountRadial = elementsCountAlongVCInlet, + meshGroups = [ raMeshGroup, ivcInletMeshGroup if (v == 0) else svcInletMeshGroup]) + + # create left atrial appendage + position = laTrackSurface.createPositionProportion(laaMidpointOver, laaMidpointLeft) + laamx, d1, d2 = laTrackSurface.evaluateCoordinates(position, derivatives = True) + # force d2 to be vertical, d3, d1 to be horizontal + laamd2 = [ 0.0, 0.0, 1.0 ] + laamd3 = vector.normalise(vector.crossproduct3(d2, laamd2)) + laamd1 = vector.crossproduct3(laamd2, laamd3) if False: node = nodes.createNode(nodeIdentifier, nodetemplate) cache.setNode(node) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, ivcCentrex) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, ivcCentred1) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, ivcCentred2) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, ivcCentred3) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, laamx ) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, laamd1) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, laamd2) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, laamd3) nodeIdentifier += 1 + elementsCountAroundLaa = elementsCountAroundLeftAtrialAppendageBase + elementsCountAroundLeftAtriumRPV + elementsCountOverSideLeftAtriumLPV + 1 + if commonLeftRightPvOstium: + elementsCountAroundLaa += 1 + #print('elementsCountAroundLaa', elementsCountAroundLaa) + # get start points, nodes, derivative maps + laasx = [ [ None ]*elementsCountAroundLaa, [ None ]*elementsCountAroundLaa ] + laasd1 = [ [ None ]*elementsCountAroundLaa, [ None ]*elementsCountAroundLaa ] + laasd2 = [ [ None ]*elementsCountAroundLaa, [ None ]*elementsCountAroundLaa ] + laasd3 = [ [ None ]*elementsCountAroundLaa, [ None ]*elementsCountAroundLaa ] + laasNodeId = [ [ None ]*elementsCountAroundLaa, [ None ]*elementsCountAroundLaa ] + laasDerivativesMap = [ [ None ]*elementsCountAroundLaa, [ None ]*elementsCountAroundLaa ] + # set points anticlockwise around base first, starting at aorta + ixStart = 1 + elementsCountAroundLaa%2 # position in final array for aorta base + ixRotation = round(elementsCountAroundLaa*0.5*laaAngleAxialRadians/math.pi) # rotate indexes to align with axial angle + ix = (ixStart - ixRotation) % elementsCountAroundLaa - elementsCountAroundLaa # works for negative values as modulo is always non-negative in python + #print('laa ixStart',ixStart,'ixRotation',ixRotation,'ix',ix) + if commonLeftRightPvOstium: + nv = 1 + for n3 in range(2): + laasx [n3][ix] = lavtx [n3][nv] + laasd1[n3][ix] = lavtd1[n3][nv] + laasd2[n3][ix] = lavtd2[n3][nv] + laasd3[n3][ix] = lavtd3[n3][nv] + laasNodeId[n3][ix] = lavtNodeId[n3][nv] + laasDerivativesMap[n3][ix] = ( (0, -1, 0), (1, 0, 0), None) + ix += 1 + # left along base + for n1 in range(elementsCountAroundLeftAtrialAppendageBase + 1): + nb = n1 + elementsCountAroundLeftAtriumAorta + for n3 in range(2): + laasx [n3][ix] = labx [n3][nb] + laasd1[n3][ix] = labd1[n3][nb] + laasd2[n3][ix] = labd2[n3][nb] + laasd3[n3][ix] = labd3[n3][nb] + laasNodeId[n3][ix] = labNodeId[n3][nb] + if n1 == 0: + laasDerivativesMap[n3][ix] = ( (0, -1, 0), (1, 1, 0), None, None ) + elif n1 == elementsCountAroundLeftAtrialAppendageBase: + laasDerivativesMap[n3][ix] = ( None, (-1, 1, 0), None, (0, 1, 0 ) ) + else: + laasDerivativesMap[n3][ix] = ( None, None, None ) + ix += 1 + # right over appendage laoa + for n1 in range(elementsCountAroundLeftAtriumRPV + elementsCountOverSideLeftAtriumLPV): + no = -1 - n1 + for n3 in range(2): + laasx [n3][ix] = laoax [n3][no] + laasd1[n3][ix] = laoad1[n3][no] + laasd2[n3][ix] = laoad2[n3][no] + laasd3[n3][ix] = laoad3[n3][no] + laasNodeId[n3][ix] = laoaNodeId[n3][no] + if n1 == 0: + laasDerivativesMap[n3][ix] = ( (0, 1, 0), (-1, 0, 0), None ) + elif n1 == (elementsCountAroundLeftAtriumRPV + elementsCountOverSideLeftAtriumLPV - 1): + laasDerivativesMap[n3][ix] = ( (-1, 0, 0), (1, -1, 0), None, (0, -1, 0 ) ) + else: + laasDerivativesMap[n3][ix] = ( (-1, 0, 0), (0, -1, 0), None ) + ix += 1 + #print('laasNodeId[0]',laasNodeId[0]) + #print('laasNodeId[1]',laasNodeId[1]) + #print('laasDerivativesMap[0]',laasDerivativesMap[0]) + #print('laasDerivativesMap[1]',laasDerivativesMap[1]) + elementsCountLaaRadial = 2 + # get end points, nodes, derivative maps, expanding from wedge + laawx, laawd1, laawd2, laawd3, elementsCountAcrossLaaWedge, laawPointsMap, laaeDerivativesMap = \ + getAtrialAppendageWedgePoints(laamx, laamd1, laamd2, laamd3, laaAngleLeftRadians, laaAngleUpradians, laaAngleAxialRadians, laaBaseLength, + elementsCountAroundLaa, elementsCountLaaRadial, laaArcLength, laaArcRadius, laaWallThickness, laaWedgeAngleRadians) + # create laa wedge nodes: + laawNodeId = [ [], [] ] + for n3 in range(2): + for n1 in range(len(laawx[n3])): + node = nodes.createNode(nodeIdentifier, nodetemplate) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, laawx [n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, laawd1[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, laawd2[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, laawd3[n3][n1]) + laawNodeId[n3].append(nodeIdentifier) + nodeIdentifier += 1 + laaex = [ [ None ]*elementsCountAroundLaa, [ None ]*elementsCountAroundLaa ] + laaed1 = [ [ None ]*elementsCountAroundLaa, [ None ]*elementsCountAroundLaa ] + laaed2 = [ [ None ]*elementsCountAroundLaa, [ None ]*elementsCountAroundLaa ] + laaed3 = [ [ None ]*elementsCountAroundLaa, [ None ]*elementsCountAroundLaa ] + laaeNodeId = [ [ None ]*elementsCountAroundLaa, [ None ]*elementsCountAroundLaa ] + for n1 in range(elementsCountAroundLaa): + nw = laawPointsMap[n1] + for n3 in range(2): + laaex [n3][n1] = laawx [n3][nw] + laaed1[n3][n1] = laawd1[n3][nw] + laaed2[n3][n1] = laawd2[n3][nw] + laaed3[n3][n1] = laawd3[n3][nw] + laaeNodeId[n3][n1] = laawNodeId[n3][nw] + #print('laaeNodeId[0]',laaeNodeId[0]) + #print('laaeNodeId[1]',laaeNodeId[1]) + #print('laaeDerivativesMap[0]',laaeDerivativesMap[0]) + #print('laaeDerivativesMap[1]',laaeDerivativesMap[1]) + nodeIdentifier, elementIdentifier = createAnnulusMesh3d( + nodes, mesh, nodeIdentifier, elementIdentifier, + laasx, laasd1, laasd2, laasd3, laasNodeId, laasDerivativesMap, + laaex, laaed1, laaed2, laaed3, laaeNodeId, laaeDerivativesMap, + forceMidLinearXi3 = True, forceEndLinearXi3 = True, + maxStartThickness = laaWallThickness, + elementsCountRadial = elementsCountLaaRadial, + meshGroups = [ laMeshGroup, laaMeshGroup ]) + + # create right atrial appendage 'plain' elements + meshGroups = [ raMeshGroup, raaMeshGroup ] + for e2 in range(2): + for e1 in range(elementsCountOverSideRightAtriumPouch): + eft1 = eft + elementtemplate1 = elementtemplate + scalefactors = None + nc = 2 + e1 + if e2 == 0: + nids = [ ractNodeId[0][nc], ractNodeId[0][nc + 1], raaqNodeId[0][e1], raaqNodeId[0][e1 + 1], + ractNodeId[1][nc], ractNodeId[1][nc + 1], raaqNodeId[1][e1], raaqNodeId[1][e1 + 1] ] + else: + nids = [ raaqNodeId[0][e1], raaqNodeId[0][e1 + 1], raapNodeId[0][e1], raapNodeId[0][e1 + 1], + raaqNodeId[1][e1], raaqNodeId[1][e1 + 1], raapNodeId[1][e1], raapNodeId[1][e1 + 1] ] + if e1 == 0: + if e2 == 0: + continue + nids[0] = ractNodeId[0][nc + 1] + nids[4] = ractNodeId[1][nc + 1] + eft1 = tricubichermite.createEftNoCrossDerivatives() + setEftScaleFactorIds(eft1, [1], []) + scalefactors = [ -1.0 ] + remapEftNodeValueLabel(eft1, [ 1, 5 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [1] ) ]) + remapEftNodeValueLabel(eft1, [ 1, 5 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS2, [1] ) ]) + remapEftNodeValueLabel(eft1, [ 2, 6 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) + remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) + remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS2, [1] ) ]) + elif (e2 == 1) and (e1 < (elementsCountOverSideRightAtriumPouch - 1)): + pass # regular elements + else: + eft1 = tricubichermite.createEftNoCrossDerivatives() + setEftScaleFactorIds(eft1, [1], []) + scalefactors = [ -1.0 ] + if e1 < (elementsCountOverSideRightAtriumPouch - 1): + if e2 == 0: + remapEftNodeValueLabel(eft1, [ 1, 2, 5, 6 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ) ]) + remapEftNodeValueLabel(eft1, [ 1, 2, 5, 6 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [] ) ]) + if e1 == 1: + remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) + else: + if e2 == 0: + remapEftNodeValueLabel(eft1, [ 1, 5 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ) ]) + remapEftNodeValueLabel(eft1, [ 1, 5 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [] ) ]) + remapEftNodeValueLabel(eft1, [ 2, 4, 6, 8 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [1] ) ]) + remapEftNodeValueLabel(eft1, [ 2, 4, 6, 8 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ) ]) + if eft1 is not eft: + elementtemplateX.defineField(coordinates, -1, eft1) + elementtemplate1 = elementtemplateX + element = mesh.createElement(elementIdentifier, elementtemplate1) + result2 = element.setNodesByIdentifier(eft1, nids) + if scalefactors: + result3 = element.setScaleFactors(eft1, scalefactors) + else: + result3 = '-' + #print('create element raa plain', element.isValid(), elementIdentifier, result2, result3, nids) + elementIdentifier += 1 + for meshGroup in meshGroups: + meshGroup.addElement(element) + # create right atrial appendage 'pouch' + position = raTrackSurface.createPositionProportion(1.0 - raaMidpointOver, raaMidpointRight) + raamx, d1, d2 = raTrackSurface.evaluateCoordinates(position, derivatives = True) + # force d2 to be vertical, d3, d1 to be horizontal + raamd2 = [ 0.0, 0.0, 1.0 ] + raamd3 = vector.normalise(vector.crossproduct3(raamd2, d2)) + raamd1 = vector.crossproduct3(raamd2, raamd3) + if False: node = nodes.createNode(nodeIdentifier, nodetemplate) cache.setNode(node) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, svcCentrex) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, svcCentred1) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, svcCentred2) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, svcCentred3) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, raamx ) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, raamd1) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, raamd2) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, raamd3) nodeIdentifier += 1 - - # create vena cavae - - elementsCountAcrossInlet = elementsCountAcrossVC - for i in range(2 if addInlets else 0): - if i == 0: # ivc - elementsCountUpInlet = elementsCountUpIVC - elementsCountAroundInlet = elementsCountAroundIVC - inletCentrex = ivcCentrex - inletCentred1 = ivcCentred1 - inletCentred2 = ivcCentred2 - inletCentred3 = ivcCentred3 - inletInnerRadius = ivcInnerRadius - inletWallThickness = ivcWallThickness - e1min = ivce1min - e2min = ivce2min - else: # svc - elementsCountUpInlet = elementsCountUpSVC - elementsCountAroundInlet = elementsCountAroundSVC - inletCentrex = svcCentrex - inletCentred1 = svcCentred1 - inletCentred2 = svcCentred2 - inletCentred3 = svcCentred3 - inletInnerRadius = svcInnerRadius - inletWallThickness = svcWallThickness - e1min = svce1min - e2min = svce2min - - startRadians = elementsCountUpInlet*math.pi/elementsCountAroundInlet - inletStartInnerx, inletStartInnerd1 = createCirclePoints(inletCentrex, inletCentred1, inletCentred2, elementsCountAroundInlet, startRadians) - inletStartOuterx, inletStartOuterd1 = createCirclePoints(inletCentrex, vector.setMagnitude(inletCentred1, inletInnerRadius + inletWallThickness), \ - vector.setMagnitude(inletCentred2, inletInnerRadius + inletWallThickness), elementsCountAroundInlet, startRadians) - inletStartx = [ inletStartInnerx , inletStartOuterx ] - inletStartd1 = [ inletStartInnerd1, inletStartOuterd1 ] - inletStartd2 = [ [ inletCentred3 ]*elementsCountAroundInlet ]*2 - - inletEndx = [ [], [] ] - inletEndd1 = [ [], [] ] - inletEndd2 = [ [], [] ] - inletEndd3 = [ [], [] ] - inletEndNodeId = [ [], [] ] - inletEndDerivativesMap = [ [], [] ] - n1lmin = elementsCountAroundAtrialFreeWall - e1min - elementsCountAcrossInlet - n1lmax = elementsCountAroundAtrialFreeWall - e1min - n1rmin = ran1FreeWallStart + e1min - n1rmax = n1rmin + elementsCountAcrossInlet + elementsCountAroundRaa = elementsCountAroundRightAtrialAppendagePlainBase + elementsCountAroundRightAtrialAppendagePouchBase + elementsCountOverAtria - 2 + #print('elementsCountAroundRaa', elementsCountAroundRaa) + # get start points, nodes, derivative maps + raasx = [ [ None ]*elementsCountAroundRaa, [ None ]*elementsCountAroundRaa ] + raasd1 = [ [ None ]*elementsCountAroundRaa, [ None ]*elementsCountAroundRaa ] + raasd2 = [ [ None ]*elementsCountAroundRaa, [ None ]*elementsCountAroundRaa ] + raasd3 = [ [ None ]*elementsCountAroundRaa, [ None ]*elementsCountAroundRaa ] + raasNodeId = [ [ None ]*elementsCountAroundRaa, [ None ]*elementsCountAroundRaa ] + raasDerivativesMap = [ [ None ]*elementsCountAroundRaa, [ None ]*elementsCountAroundRaa ] + # set points anticlockwise around base first from aorta + ixStart = 2 + elementsCountAroundRaa % 2 # position in final array for first base on raap + ixRotation = round(elementsCountAroundRaa*0.5*raaAngleAxialRadians/math.pi) # rotate indexes to align with axial angle + ix = (ixStart - ixRotation) % elementsCountAroundRaa - elementsCountAroundRaa # works for negative values as modulo is always non-negative in python + #print('raa ixStart',ixStart,'ixRotation',ixRotation,'ix',ix) + # left/anticlockwise along base + for n1 in range(elementsCountAroundRightAtrialAppendagePouchBase + 1): + nb = elementsCountAroundRightAtriumPosteriorVenous + elementsCountAroundRightAtrialAppendagePlainBase + n1 for n3 in range(2): - if n3 == 0: - lax = laInnerx - lad1 = laInnerd1 - lad2 = laInnerd2 - lad3 = laInnerd3 + raasx [n3][ix] = rabx [n3][nb] + raasd1[n3][ix] = rabd1[n3][nb] + raasd2[n3][ix] = rabd2[n3][nb] + raasd3[n3][ix] = rabd3[n3][nb] + raasNodeId[n3][ix] = rabNodeId[n3][nb] + if n1 == 0: + raasDerivativesMap[n3][ix] = ( (0, -1, 0), (1, 1, 0), None, None ) + elif n1 == elementsCountAroundRightAtrialAppendagePouchBase: + raasDerivativesMap[n3][ix] = ( None, (-1, 1, 0), None, (0, 1, 0 ) ) else: - lax = laOuterx - lad1 = laOuterd1 - lad2 = laOuterd2 - lad3 = laOuterd3 - for n in range(elementsCountAroundInlet): - if n < elementsCountAcrossInlet: - # left - derivativesMap = [ ( 0, -1, 0 ), ( 1, -1, 0 ), None, ( -1, 0, 0 ) ] if (n == 0) else [ ( -1, 0, 0 ), ( 0, -1, 0 ), None ] - n1l = n1lmin + n - n1r = n1rmax - n - n2 = e2min - elif n < (elementsCountAcrossInlet + elementsCountUpInlet): - # up - derivativesMap = [ ( -1, 0, 0 ), ( -1, -1, 0 ), None, ( 0, 1, 0 ) ] if (n == elementsCountAcrossInlet) else [ ( 0, 1, 0 ), ( -1, 0, 0 ), None ] - n1l = n1lmax - n1r = n1rmin - n2 = e2min + n - elementsCountAcrossInlet - elif n < (elementsCountAcrossInlet*2 + elementsCountUpInlet): - # right - n1 = n - (elementsCountAcrossInlet + elementsCountUpInlet) - derivativesMap = [ ( 0, 1, 0 ), ( -1, 1, 0 ), None, ( 1, 0, 0 ) ] if (n1 == 0) else [ None, None, None ] - n1l = n1lmax - n1 - n1r = n1rmin + n1 - n2 = e2min + elementsCountUpInlet - else: - # down - n2 = elementsCountAroundInlet - n - derivativesMap = [ ( 1, 0, 0 ), ( 1, 1, 0 ), None, ( 0, -1, 0 ) ] if (n2 == elementsCountUpInlet) else [ ( 0, -1, 0 ), ( 1, 0, 0 ), None ] - n1l = n1lmin - n1r = n1rmax - n2 = e2min + elementsCountAroundInlet - n - if (i == 0) and (n3 == 1) and (n1r == n1rmin): - # fix ivc derivative 3 mapping adjacent to collapsed nodes maps to -ds1 + ds3 - derivativesMap[2] = ( -1, 0, 1 ) - elif (i == 1) and (n3 == 1) and (n1r == n1rmax): - # fix svc derivative 3 mapping adjacent to collapsed nodes maps to ds1 + ds3 - derivativesMap[2] = ( 1, 0, 1 ) - # mirror from left to right atrium - inletEndx [n3].append([ -lax [n2][n1l][0], lax [n2][n1l][1], lax [n2][n1l][2] ]) - inletEndd1[n3].append([ lad1[n2][n1l][0], -lad1[n2][n1l][1], -lad1[n2][n1l][2] ]) - inletEndd2[n3].append([ -lad2[n2][n1l][0], lad2[n2][n1l][1], lad2[n2][n1l][2] ]) - inletEndd3[n3].append([ -lad3[n2][n1l][0], lad3[n2][n1l][1], lad3[n2][n1l][2] ]) - #print('n',n,': n3',n3,'n2',n2,'n1r',n1r) - inletEndNodeId[n3].append(raNodeId[n3][n2][n1r]) - inletEndDerivativesMap[n3].append(derivativesMap) - - nodeIdentifier, elementIdentifier = tricubichermite.createAnnulusMesh3d( - inletStartx, inletStartd1, inletStartd2, None, None, None, - inletEndx, inletEndd1, inletEndd2, inletEndd3, inletEndNodeId, inletEndDerivativesMap, - nodetemplate, nodetemplateLinearS3, nodeIdentifier, elementIdentifier, - elementsCountRadial = elementsCountInlet, maxEndThickness = 1.5*aFreeWallThickness, - meshGroups = [ raMeshGroup, ivcInletMeshGroup if (i == 0) else svcInletMeshGroup]) + raasDerivativesMap[n3][ix] = ( None, None, None ) + ix += 1 + # back over crista terminalis ract + for n1 in range(2): + nc = 1 + n1 + for n3 in range(2): + raasx [n3][ix] = ractx [n3][nc] + raasd1[n3][ix] = ractd1[n3][nc] + raasd2[n3][ix] = ractd2[n3][nc] + raasd3[n3][ix] = ractd3[n3][nc] + raasNodeId[n3][ix] = ractNodeId[n3][nc] + if n1 == 0: + raasDerivativesMap[n3][ix] = ( None, (-1, 0, 0), None, (0, 1, 0) ) # compare ( None, (-1, 1, 0), None, (0, 1, 0) ) + else: # n1 == 1: + raasDerivativesMap[n3][ix] = ( (0, 1, 0), (-1, -1, 0), None, (-1, 1, 0) ) # compare ( (0, 1, 0), (-1, 0, 0), None, (-1, 1, 0) ) + ix += 1 + # down right atrial appendage pouch limit, raap + n1Last = len(raapx[1]) - 1 + for n1 in range(1, n1Last + 1): + for n3 in range(2): + raasx [n3][ix] = raapx [n3][n1] + raasd1[n3][ix] = raapd1[n3][n1] + raasd2[n3][ix] = raapd2[n3][n1] + raasd3[n3][ix] = raapd3[n3][n1] + raasNodeId[n3][ix] = raapNodeId[n3][n1] + if n1 == n1Last: + raasDerivativesMap[n3][ix] = ( (0, -1, 0), (1, 0, 0), None ) + else: + raasDerivativesMap[n3][ix] = ( (1, 0, 0), (0, 1, 0), None ) + ix += 1 + #print('raasNodeId[0]',raasNodeId[0]) + #print('raasNodeId[1]',raasNodeId[1]) + #print('raasDerivativesMap[0]',raasDerivativesMap[0]) + #print('raasDerivativesMap[1]',raasDerivativesMap[1]) + elementsCountRaaRadial = 2 + # get end points, nodes, derivative maps, expanding from wedge + raawx, raawd1, raawd2, raawd3, elementsCountAcrossRaaWedge, raawPointsMap, raaeDerivativesMap = \ + getAtrialAppendageWedgePoints(raamx, raamd1, raamd2, raamd3, raaAngleLeftRadians, raaAngleUpradians, raaAngleAxialRadians, raaBaseLength, + elementsCountAroundRaa, elementsCountRaaRadial, raaArcLength, raaArcRadius, raaWallThickness, raaWedgeAngleRadians) + # create raa wedge nodes: + raawNodeId = [ [], [] ] + for n3 in range(2): + for n1 in range(len(raawx[n3])): + node = nodes.createNode(nodeIdentifier, nodetemplate) + cache.setNode(node) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, raawx [n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, raawd1[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, raawd2[n3][n1]) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, raawd3[n3][n1]) + raawNodeId[n3].append(nodeIdentifier) + nodeIdentifier += 1 + raaex = [ [ None ]*elementsCountAroundRaa, [ None ]*elementsCountAroundRaa ] + raaed1 = [ [ None ]*elementsCountAroundRaa, [ None ]*elementsCountAroundRaa ] + raaed2 = [ [ None ]*elementsCountAroundRaa, [ None ]*elementsCountAroundRaa ] + raaed3 = [ [ None ]*elementsCountAroundRaa, [ None ]*elementsCountAroundRaa ] + raaeNodeId = [ [ None ]*elementsCountAroundRaa, [ None ]*elementsCountAroundRaa ] + for n1 in range(elementsCountAroundRaa): + nw = raawPointsMap[n1] + for n3 in range(2): + raaex [n3][n1] = raawx [n3][nw] + raaed1[n3][n1] = raawd1[n3][nw] + raaed2[n3][n1] = raawd2[n3][nw] + raaed3[n3][n1] = raawd3[n3][nw] + raaeNodeId[n3][n1] = raawNodeId[n3][nw] + #print('raaeNodeId[0]',raaeNodeId[0]) + #print('raaeNodeId[1]',raaeNodeId[1]) + #print('raaeDerivativesMap[0]',raaeDerivativesMap[0]) + #print('raaeDerivativesMap[1]',raaeDerivativesMap[1]) + nodeIdentifier, elementIdentifier = createAnnulusMesh3d( + nodes, mesh, nodeIdentifier, elementIdentifier, + raasx, raasd1, raasd2, raasd3, raasNodeId, raasDerivativesMap, + raaex, raaed1, raaed2, raaed3, raaeNodeId, raaeDerivativesMap, + forceMidLinearXi3 = True, forceEndLinearXi3 = True, + maxStartThickness = raaWallThickness, + elementsCountRadial = elementsCountRaaRadial, + meshGroups = [ raMeshGroup, raaMeshGroup ]) + + if drawLaTrackSurface: + mesh2d = fm.findMeshByDimension(2) + bicubicHermiteBasis = fm.createElementbasis(2, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE) + eft2d = mesh2d.createElementfieldtemplate(bicubicHermiteBasis) + # remove cross derivative 12 + for n in range(4): + r = eft2d.setFunctionNumberOfTerms(n*4 + 4, 0) + elementtemplate2d = mesh2d.createElementtemplate() + elementtemplate2d.setElementShapeType(Element.SHAPE_TYPE_SQUARE) + elementtemplate2d.defineField(coordinates, -1, eft2d) + nodesCount1 = laTrackSurface.elementsCount1 + 1 + for e2 in range(laTrackSurface.elementsCount2): + for e1 in range(laTrackSurface.elementsCount1): + element = mesh2d.createElement(-1, elementtemplate2d) # since on 2-D mesh + nid1 = laTrackSurfaceFirstNodeIdentifier + e2*nodesCount1 + e1 + element.setNodesByIdentifier(eft2d, [ nid1, nid1 + 1, nid1 + nodesCount1, nid1 + nodesCount1 + 1 ]) + if drawRaTrackSurface: + mesh2d = fm.findMeshByDimension(2) + bicubicHermiteBasis = fm.createElementbasis(2, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE) + eft2d = mesh2d.createElementfieldtemplate(bicubicHermiteBasis) + # remove cross derivative 12 + for n in range(4): + r = eft2d.setFunctionNumberOfTerms(n*4 + 4, 0) + elementtemplate2d = mesh2d.createElementtemplate() + elementtemplate2d.setElementShapeType(Element.SHAPE_TYPE_SQUARE) + elementtemplate2d.defineField(coordinates, -1, eft2d) + nodesCount1 = raTrackSurface.elementsCount1 + 1 + for e2 in range(raTrackSurface.elementsCount2): + for e1 in range(raTrackSurface.elementsCount1): + element = mesh2d.createElement(-1, elementtemplate2d) # since on 2-D mesh + nid1 = raTrackSurfaceFirstNodeIdentifier + e2*nodesCount1 + e1 + element.setNodesByIdentifier(eft2d, [ nid1, nid1 + 1, nid1 + nodesCount1, nid1 + nodesCount1 + 1 ]) fm.endChange() return annotationGroups @@ -1783,14 +3174,12 @@ def refineMesh(cls, meshrefinement, options): :param options: Dict containing options. See getDefaultOptions(). """ assert isinstance(meshrefinement, MeshRefinement) - elementsCountAroundAtrialFreeWall = options['Number of elements around atrial free wall'] elementsCountAroundAtrialSeptum = options['Number of elements around atrial septum'] - elementsCountAroundAtria = elementsCountAroundAtrialFreeWall + elementsCountAroundAtrialSeptum - elementsCountUpAtria = options['Number of elements up atria'] - elementsCountInlet = options['Number of elements inlet'] + elementsCountAroundLeftAtriumFreeWall = options['Number of elements around left atrium free wall'] + elementsCountAroundRightAtriumFreeWall = options['Number of elements around right atrium free wall'] refineElementsCountSurface = options['Refine number of elements surface'] refineElementsCountThroughWall = options['Refine number of elements through wall'] - element = meshrefinement._sourceElementiterator.next() + sourceFm = meshrefinement._sourceFm annotationGroups = meshrefinement._sourceAnnotationGroups laGroup = findAnnotationGroupByName(annotationGroups, 'left atrium') @@ -1800,30 +3189,36 @@ def refineMesh(cls, meshrefinement, options): aSeptumGroup = findAnnotationGroupByName(annotationGroups, 'interatrial septum') aSeptumElementGroupField = aSeptumGroup.getFieldElementGroup(meshrefinement._sourceMesh) isSeptumEdgeWedge = sourceFm.createFieldXor(sourceFm.createFieldAnd(laElementGroupField, raElementGroupField), aSeptumElementGroupField) - # last atria element is last element in svc inlet group: - svcInletGroup = findAnnotationGroupByName(annotationGroups, 'superior vena cava inlet') - svcInletMeshGroup = svcInletGroup.getMeshGroup(meshrefinement._sourceMesh) - lastSvcInletElementIdentifier = -1 - elementIter = svcInletMeshGroup.createElementiterator() - tmpElement = elementIter.next() - while tmpElement.isValid(): - lastSvcInletElementIdentifier = tmpElement.getIdentifier() - tmpElement = elementIter.next() - cache = sourceFm.createFieldcache() + # last atria element is last element in following group: + lastGroup = findAnnotationGroupByName(annotationGroups, 'right atrial appendage') + lastMeshGroup = lastGroup.getMeshGroup(meshrefinement._sourceMesh) + lastElementIdentifier = -1 + elementIter = lastMeshGroup.createElementiterator() + element = elementIter.next() + while element.isValid(): + lastElementIdentifier = element.getIdentifier() + element = elementIter.next() - refineElements2 = refineElementsCountSurface + cache = sourceFm.createFieldcache() refineElements3 = refineElementsCountThroughWall + element = meshrefinement._sourceElementiterator.next() + wedgeElementCount = 0 while element.isValid(): elementIdentifier = element.getIdentifier() + refineElements1 = refineElementsCountSurface + refineElements2 = refineElementsCountSurface cache.setElement(element) result, isWedge = isSeptumEdgeWedge.evaluateReal(cache, 1) if isWedge: - refineElements1 = refineElementsCountThroughWall - else: - refineElements1 = refineElementsCountSurface + wedgeElementCount += 1 + # the first two around the base are collapsed on 1-3, remainder on 2-3 + if wedgeElementCount <= 2: + refineElements1 = refineElementsCountThroughWall + else: + refineElements2 = refineElementsCountThroughWall meshrefinement.refineElementCubeStandard3d(element, refineElements1, refineElements2, refineElements3) - if elementIdentifier == lastSvcInletElementIdentifier: + if elementIdentifier == lastElementIdentifier: return # finish on last so can continue elsewhere element = meshrefinement._sourceElementiterator.next() @@ -1844,17 +3239,100 @@ def generateMesh(cls, region, options): return meshrefinement.getAnnotationGroups() -def getLeftAtriumBasePoints(elementsCountAroundAtrialFreeWall, elementsCountAroundAtrialSeptum, - aBaseInnerMajorMag, aBaseInnerMinorMag, aMajorAxisRadians, - aBaseWallThickness, aBaseSlopeHeight, aBaseSlopeLength, aSeptumThickness, - aortaOuterPlusRadius, aBaseFrontInclineRadians, aBaseSideInclineRadians, aBaseBackInclineRadians): +def getLeftAtriumPulmonaryVeinOstiaElementsCounts(elementsCountAroundLeftAtriumFreeWall, elementsCountOverAtria, commonLeftRightPvOstium): + ''' + Return numbers of elements around left and right pulmonary vein ostia. + If commonLeftRightPvOstium, value is returned in elementsCountAroundLpvOstium. + :return: elementsCountAroundLpvOstium, elementsCountAroundRpvOstium + ''' + elementsCountOverAtriaCoronarySinus, \ + elementsCountOverLeftAtriumNonVenousAnterior, elementsCountOverLeftAtriumVenous, elementsCountOverLeftAtriumNonVenousPosterior, \ + elementsCountOverRightAtriumNonVenousAnterior, elementsCountOverRightAtriumVenous, elementsCountOverRightAtriumNonVenousPosterior \ + = getOverAtriaElementsCounts(elementsCountOverAtria) + elementsCountAroundLeftAtriumAorta, elementsCountAroundLeftAtrialAppendageBase, elementsCountAroundLeftAtriumLPV, elementsCountAroundLeftAtriumRPV \ + = getLeftAtriumBaseFreewallElementsCounts(elementsCountAroundLeftAtriumFreeWall) + elementsCountAroundRpvOstium = 2*(elementsCountOverLeftAtriumVenous + elementsCountAroundLeftAtriumRPV) + if commonLeftRightPvOstium: + elementsCountAroundLpvOstium = elementsCountOverLeftAtriumVenous + 2*(elementsCountAroundLeftAtriumLPV + elementsCountAroundLeftAtriumRPV) + else: + elementsCountAroundLpvOstium = elementsCountOverLeftAtriumVenous + 2 + 2*elementsCountAroundLeftAtriumLPV + return elementsCountAroundLpvOstium, elementsCountAroundRpvOstium + + +def getLeftAtriumBaseFreewallElementsCounts(elementsCountAroundLeftAtriumFreeWall): + ''' + Get the number of elements in each section of the left atrium free wall. + :param elementsCountAroundLeftAtriumFreeWall: Valid range 6-10. + :return: elementsCountAroundLeftAtriumAorta, elementsCountAroundLeftAtrialAppendageBase, + elementsCountAroundLeftAtriumLPV, elementsCountAroundLeftAtriumRPV + ''' + assert 6 <= elementsCountAroundLeftAtriumFreeWall <= 10, \ + 'getLeftAtriumBaseFreewallElementsCounts: elements count out of range: ' + str(elementsCountAroundLeftAtriumFreeWall) + elementsCountAroundLeftAtriumAorta = 1 + elementsCountAroundLeftAtriumVP = (elementsCountAroundLeftAtriumFreeWall + 1)//2 + elementsCountAroundLeftAtrialAppendageBase = elementsCountAroundLeftAtriumFreeWall - elementsCountAroundLeftAtriumVP - elementsCountAroundLeftAtriumAorta + elementsCountAroundLeftAtriumRPV = elementsCountAroundLeftAtriumVP//2 + elementsCountAroundLeftAtriumLPV = elementsCountAroundLeftAtriumVP - elementsCountAroundLeftAtriumRPV + return elementsCountAroundLeftAtriumAorta, elementsCountAroundLeftAtrialAppendageBase, \ + elementsCountAroundLeftAtriumLPV, elementsCountAroundLeftAtriumRPV + + +def getRightAtriumBaseFreewallElementsCounts(elementsCountAroundRightAtriumFreeWall): + ''' + Get the number of elements in each section of the right atrium free wall. + :param elementsCountAroundRightAtriumFreeWall: Valid range 6-10. + :return: elementsCountAroundRightAtriumPosteriorVenous, elementsCountAroundRightAtrialAppendagePlainBase + elementsCountAroundRightAtrialAppendagePouchBase, elementsCountAroundRightAtriumAorta + ''' + assert 6 <= elementsCountAroundRightAtriumFreeWall <= 10, \ + 'getRightAtriumBaseFreewallElementsCounts: elements count out of range: ' + str(elementsCountAroundRightAtriumFreeWall) + elementsCountAroundRightAtriumAorta = 1 + elementsCountAroundRightAtriumPosteriorVenous = elementsCountAroundRightAtriumFreeWall//4 + elementsCountAroundRightAtrialAppendagePlainBase = 2 + elementsCountAroundRightAtrialAppendagePouchBase = (elementsCountAroundRightAtriumFreeWall - elementsCountAroundRightAtrialAppendagePlainBase + - elementsCountAroundRightAtriumPosteriorVenous - elementsCountAroundRightAtriumAorta) + return elementsCountAroundRightAtriumPosteriorVenous, elementsCountAroundRightAtrialAppendagePlainBase, \ + elementsCountAroundRightAtrialAppendagePouchBase, elementsCountAroundRightAtriumAorta + + +def getOverAtriaElementsCounts(elementsCountOverAtria): + ''' + Get the number of elements in each section over the atria, at the outer + interatrial groove. + :param elementsCountOverAtria: Valid values 6 or 8. + :return: elementsCountOverAtriaCoronarySinus, + elementsCountOverLeftAtriumNonVenousAnterior, elementsCountOverLeftAtriumVenous, elementsCountOverLeftAtriumNonVenousPosterior, + elementsCountOverRightAtriumNonVenousAnterior, elementsCountOverRightAtriumVenous, elementsCountOverRightAtriumNonVenousPosterior + ''' + assert elementsCountOverAtria in [ 6, 8 ], \ + 'getOverAtriaElementsCounts: elements count not 6 or 8: ' + str(elementsCountOverAtria) + elementsCountOverAtriaCoronarySinus = 1 + elementsCountOverLeftAtriumNonVenousAnterior = 2 + elementsCountOverLeftAtriumNonVenousPosterior = 2 + elementsCountOverLeftAtriumVenous = elementsCountOverAtria - elementsCountOverLeftAtriumNonVenousPosterior - elementsCountOverLeftAtriumNonVenousAnterior + elementsCountOverRightAtriumNonVenousAnterior = 1 + elementsCountOverRightAtriumNonVenousPosterior = 1 + elementsCountOverRightAtriumVenous = elementsCountOverAtria - elementsCountOverRightAtriumNonVenousPosterior - elementsCountOverRightAtriumNonVenousAnterior + return elementsCountOverAtriaCoronarySinus, \ + elementsCountOverLeftAtriumNonVenousAnterior, elementsCountOverLeftAtriumVenous, elementsCountOverLeftAtriumNonVenousPosterior, \ + elementsCountOverRightAtriumNonVenousAnterior, elementsCountOverRightAtriumVenous, elementsCountOverRightAtriumNonVenousPosterior + +def getAtriumBasePoints(elementsCountAroundAtrialSeptum, elementsCountAroundLeftAtriumFreeWall, elementsCountAroundRightAtriumFreeWall, + aBaseInnerMajorMag, aBaseInnerMinorMag, aMajorAxisRadians, + aBaseWallThickness, aBaseSlopeHeight, aBaseSlopeLength, aSeptumLength, aSeptumThickness, + aortaOuterPlusRadius, aBaseFrontInclineRadians, aBaseSideInclineRadians, aBaseBackInclineRadians, + laaLeft, laVenousMidpointLeft, raVenousRight, raaPouchRight, elementsCountAroundTrackSurface): """ - Get points around left atrium based on an ellipse. Points start from central fibrous body - and wind anticlockwise around LA. Both the cfb and crux are collapsed at the septum. - :return: laCentre, laSeptumRadians, laBaseInnerx, laBaseInnerd1, laBaseInnerd2, laBaseOuterx, laBaseOuterd1, laBaseOuterd2. + Get points around left and right atria based on an ellipse. + Left atria points start from central fibrous body and wind anticlockwise. + Right atria points start from crux and wind anticlockwise. + around LA. Both the cfb and crux are collapsed at the septum. + Also return la free wall sample outer points used in construction and for defining track surface. + :return: laBasex[n3], laBased1[n3], laBased2[n3], laBased3[n3], + raBasex[n3], raBased1[n3], raBased2[n3], raBased3[n3], + ltBaseOuterx, ltBaseOuterd1, ltBaseOuterd2, + aSeptumBaseCentre, laCentre, laSeptumRadians """ - - elementsCountAroundAtrium = elementsCountAroundAtrialFreeWall + elementsCountAroundAtrialSeptum lvOutletFrontInclineRadians = aBaseFrontInclineRadians # for now aBaseOuterMajorMag = aBaseInnerMajorMag + aBaseSlopeLength @@ -1862,18 +3340,20 @@ def getLeftAtriumBasePoints(elementsCountAroundAtrialFreeWall, elementsCountArou # following are angles in radians around LA ellipse from major axis axInner = aBaseInnerMajorMag*math.cos(aMajorAxisRadians) + ayInner = -aBaseInnerMajorMag*math.sin(aMajorAxisRadians) bxInner = aBaseInnerMinorMag*math.sin(aMajorAxisRadians) + byInner = aBaseInnerMinorMag*math.cos(aMajorAxisRadians) laSeptumRadians = math.atan2(bxInner, axInner) laCentreX = -0.5*aSeptumThickness - axInner*math.cos(laSeptumRadians) - bxInner*math.sin(laSeptumRadians) - #laCfbLeftRadians = updateEllipseAngleByArcLength(aBaseInnerMajorMag, aBaseInnerMinorMag, laSeptumRadians, \ - # (aSeptumBaseLength/elementsCountAroundAtrialSeptum)*(0.5*elementsCountAroundAtrialSeptum + 1.0)) axOuter = aBaseOuterMajorMag*math.cos(aMajorAxisRadians) + ayOuter = -aBaseOuterMajorMag*math.sin(aMajorAxisRadians) bxOuter = aBaseOuterMinorMag*math.sin(aMajorAxisRadians) + byOuter = aBaseOuterMinorMag*math.cos(aMajorAxisRadians) # get points on central fibrous body centre and cfbLeft (60 degrees clockwise around aorta) - # rotates about centre of aorta by aBaseFrontInclineRadians - cosFrontInclineRadians = math.cos(aBaseFrontInclineRadians) - sinFrontInclineRadians = math.sin(aBaseFrontInclineRadians) + # rotates about centre of aorta by lvOutletFrontInclineRadians + cosFrontInclineRadians = math.cos(lvOutletFrontInclineRadians) + sinFrontInclineRadians = math.sin(lvOutletFrontInclineRadians) pi_3 = math.pi/3.0 cosPi_3 = math.cos(pi_3) sinPi_3 = math.sin(pi_3) @@ -1886,142 +3366,486 @@ def getLeftAtriumBasePoints(elementsCountAroundAtrialFreeWall, elementsCountArou cfbY = -aortaOuterPlusRadius*cosFrontInclineRadians cfbZ = aortaOuterPlusRadius*sinFrontInclineRadians lvOutletDerivativeAround = aortaOuterPlusRadius*pi_3 - laCfbLeftRadians = getEllipseRadiansToX(axOuter, bxOuter, cfbLeftX - laCentreX, math.pi*0.5) + laCfbLeftRadians = getEllipseRadiansToX(axOuter, bxOuter, cfbLeftX - laCentreX, 0.5*math.pi) aBaseOuterMinorMagPlus = aBaseOuterMinorMag + 0.5*aBaseSlopeLength # GRC fudge factor laCentreY = cfbLeftY - math.cos(laCfbLeftRadians)*aBaseOuterMajorMag*math.sin(-aMajorAxisRadians) \ - math.sin(laCfbLeftRadians)*aBaseOuterMinorMagPlus*math.cos(-aMajorAxisRadians) laCentreZ = 0.0 - # compute radians around based on base outer major and minor axis sizes + # Convert aSeptumLength to arc angles each side of septum centre + aSeptumBaseCentreX = -0.5*aSeptumThickness + aSeptumBaseCentreY = laCentreY + ayInner*math.cos(laSeptumRadians) + byInner*math.sin(laSeptumRadians) + #print('aSeptumBaseCentreY',aSeptumBaseCentreY,'radians',laSeptumRadians) + aSeptumBaseCentreZ = -aBaseSlopeHeight + # GRC ensure aSeptumLength < min(innerminormag, innermajormag) + aSeptumAnteriorY = aSeptumBaseCentreY + 0.5*aSeptumLength + #print('ayInner',ayInner) + #print('byInner',byInner) + #print('0.5*aSeptumLength', 0.5*aSeptumLength) + #print('aSeptumBaseCentreY', aSeptumBaseCentreY) + #print('aSeptumAnteriorY', aSeptumAnteriorY) + #print('aSeptumAnteriorY - laCentreY', aSeptumAnteriorY - laCentreY) + laSeptumAnteriorRadians = getEllipseRadiansToX(ayInner, byInner, aSeptumAnteriorY - laCentreY, 0.5*(laSeptumRadians + laCfbLeftRadians)) + #print('aSeptumAnteriorY', aSeptumAnteriorY, 'laSeptumAnteriorRadians', laSeptumAnteriorRadians, 'y', + # laCentreY + ayInner*math.cos(laSeptumAnteriorRadians) + byInner*math.sin(laSeptumAnteriorRadians)) + aSeptumPosteriorY = aSeptumBaseCentreY - 0.5*aSeptumLength + laSeptumPosteriorRadians = getEllipseRadiansToX(ayInner, byInner, aSeptumPosteriorY - laCentreY, 1.5*laSeptumRadians - 0.5*laCfbLeftRadians) + #print('aSeptumPosteriorY', aSeptumPosteriorY, 'laSeptumPosteriorRadians', laSeptumPosteriorRadians, 'y', + # laCentreY + ayInner*math.cos(laSeptumPosteriorRadians) + byInner*math.sin(laSeptumPosteriorRadians)) + + # compute common lengths around outer atrialPerimeterLength = getApproximateEllipsePerimeter(aBaseOuterMajorMag, aBaseOuterMinorMag) - atrialSeptumElementLength = getEllipseArcLength(aBaseOuterMajorMag, aBaseOuterMinorMag, laSeptumRadians, laCfbLeftRadians) \ - /(0.5*elementsCountAroundAtrialSeptum + 1.0) - atrialFreeWallElementLength = (atrialPerimeterLength - atrialSeptumElementLength*(elementsCountAroundAtrialSeptum + 2)) \ - / (elementsCountAroundAtrialFreeWall - 2) - atrialTransitionElementLength = 0.5*(atrialSeptumElementLength + atrialFreeWallElementLength) - #atrialPerimeterLengthTmp = atrialSeptumElementLength*(elementsCountAroundAtrialSeptum + 1) + 2.0*atrialTransitionElementLength \ - # + (elementsCountAroundAtrialFreeWall - 3)*atrialFreeWallElementLength - #print('lengths:',(elementsCountAroundAtrialSeptum + 1),'*',atrialSeptumElementLength, '+ 2 *', \ - # atrialTransitionElementLength,'+',(elementsCountAroundAtrialFreeWall - 3),'*',atrialFreeWallElementLength, - # '=', atrialPerimeterLengthTmp, ' VS ', atrialPerimeterLength) - laRadians = [] - radiansAround = updateEllipseAngleByArcLength(aBaseOuterMajorMag, aBaseOuterMinorMag, laSeptumRadians, atrialSeptumElementLength*elementsCountAroundAtrialSeptum/2.0) - for n1 in range(elementsCountAroundAtrium): - laRadians.append(radiansAround) - if (n1 == 0) or (n1 >= elementsCountAroundAtrialFreeWall): - elementLength = atrialSeptumElementLength - elif (n1 == 1) or (n1 == (elementsCountAroundAtrialFreeWall - 1)): - elementLength = atrialTransitionElementLength - else: - elementLength = atrialFreeWallElementLength - radiansAround = updateEllipseAngleByArcLength(aBaseOuterMajorMag, aBaseOuterMinorMag, radiansAround, elementLength) + atrialSeptumInnerElementLength = getEllipseArcLength(aBaseInnerMajorMag, aBaseInnerMinorMag, laSeptumPosteriorRadians, laSeptumAnteriorRadians)/elementsCountAroundAtrialSeptum + atrialSeptumOuterElementLength = getEllipseArcLength(aBaseOuterMajorMag, aBaseOuterMinorMag, laSeptumPosteriorRadians, laSeptumAnteriorRadians)/elementsCountAroundAtrialSeptum + aSeptumCfbSideElementLength = getEllipseArcLength(aBaseOuterMajorMag, aBaseOuterMinorMag, laSeptumAnteriorRadians, laCfbLeftRadians) + aCfbSideDerivativeLength = aSeptumCfbSideElementLength # GRC was 2.0*aSeptumCfbSideElementLength - atrialSeptumOuterElementLength, but too high + aRemainingLength = atrialPerimeterLength - aSeptumCfbSideElementLength - elementsCountAroundAtrialSeptum*atrialSeptumOuterElementLength - laBaseInnerx = [] - laBaseInnerd1 = [] - laBaseInnerd2 = [] - laBaseOuterx = [] - laBaseOuterd1 = [] - laBaseOuterd2 = [] + #testradians = updateEllipseAngleByArcLength(aBaseInnerMajorMag, aBaseInnerMinorMag, laSeptumPosteriorRadians, atrialSeptumInnerElementLength) + #print('test radians 1', testradians,'vs',laSeptumRadians,'y',laCentreY + ayInner*math.cos(testradians) + byInner*math.sin(testradians), 'vs',aSeptumBaseCentreY) + #testradians = updateEllipseAngleByArcLength(aBaseInnerMajorMag, aBaseInnerMinorMag, testradians, atrialSeptumInnerElementLength) + #print('test radians 2', testradians,'vs',laSeptumAnteriorRadians,'| y',laCentreY + ayInner*math.cos(testradians) + byInner*math.sin(testradians), 'vs',aSeptumAnteriorY) baseDerivative2Scale = aortaOuterPlusRadius + # GRC use these earlier sinMajorAxisRadians = math.sin(-aMajorAxisRadians) cosMajorAxisRadians = math.cos(-aMajorAxisRadians) - # get base points on inside and outside of atria - for n3 in range(2): - if n3 == 0: - aMajorMag = aBaseInnerMajorMag - aMinorMag = aBaseInnerMinorMag - z = -aBaseSlopeHeight + # generate curves with fixed number of elements around free wall, from which track surface will be sampled + # this is needed now to sample key points defined relative to the track surface, used to set element size and spacing around + + elementsCountAroundLeftAtriumFreeWallFixed = 8 + ltFreeWallElementLength = (aRemainingLength - 0.5*(atrialSeptumOuterElementLength + aCfbSideDerivativeLength))/(elementsCountAroundLeftAtriumFreeWallFixed - 2) + # first two points are around aorta + ltBaseOuterx = [ [ cfbX, cfbY, cfbZ ], [ cfbLeftX, cfbLeftY, cfbLeftZ ] ] + ltBaseOuterd1 = [ [ -lvOutletDerivativeAround, 0.0, 0.0 ], [ -lvOutletDerivativeAround*cosPi_3, lvOutletDerivativeAround*sinPi_3*cosFrontInclineRadians, -lvOutletDerivativeAround*sinPi_3*sinFrontInclineRadians ]] + ltBaseOuterd2 = [ [ 0.0, baseDerivative2Scale*sinFrontInclineRadians, baseDerivative2Scale*cosFrontInclineRadians ], [ 0.0, baseDerivative2Scale*sinFrontInclineRadians, baseDerivative2Scale*cosFrontInclineRadians ] ] + aMajorX = aBaseOuterMajorMag*cosMajorAxisRadians + aMajorY = aBaseOuterMajorMag*sinMajorAxisRadians + aMinorX = -aBaseOuterMinorMag*sinMajorAxisRadians + aMinorY = aBaseOuterMinorMag*cosMajorAxisRadians + radiansAround = laCfbLeftRadians + sideRadians = laSeptumRadians + math.pi + backRadians = sideRadians + 0.5*math.pi + up = [ 0.0, 0.0, 1.0 ] + for n1 in range(elementsCountAroundLeftAtriumFreeWallFixed - 2): + elementLength = 0.5*(aCfbSideDerivativeLength + ltFreeWallElementLength) if (n1 == 0) else ltFreeWallElementLength + radiansAround = updateEllipseAngleByArcLength(aBaseOuterMajorMag, aBaseOuterMinorMag, radiansAround, elementLength) + cosRadiansAround = math.cos(radiansAround) + sinRadiansAround = math.sin(radiansAround) + x = [ laCentreX + cosRadiansAround*aMajorX + sinRadiansAround*aMinorX, + laCentreY + cosRadiansAround*aMajorY + sinRadiansAround*aMinorY, + 0.0 ] + d1 = vector.setMagnitude([ -sinRadiansAround*aMajorX + cosRadiansAround*aMinorX, + -sinRadiansAround*aMajorY + cosRadiansAround*aMinorY, + 0.0 ], ltFreeWallElementLength) + if radiansAround < sideRadians: + xi = (radiansAround - laCfbLeftRadians)/(sideRadians - laCfbLeftRadians) + baseInclineRadians = (1.0 - xi)*aBaseFrontInclineRadians + xi*aBaseSideInclineRadians + elif radiansAround < backRadians: + xi = (radiansAround - sideRadians)/(0.5*math.pi) + baseInclineRadians = (1.0 - xi)*aBaseSideInclineRadians + xi*aBaseBackInclineRadians else: - aMajorMag = aBaseOuterMajorMag - aMinorMag = aBaseOuterMinorMag - z = 0.0 - - aMajorX = aMajorMag*cosMajorAxisRadians - aMajorY = aMajorMag*sinMajorAxisRadians - aMinorX = -aMinorMag*sinMajorAxisRadians - aMinorY = aMinorMag*cosMajorAxisRadians - - twoPi = 2.0*math.pi - finalArcLength = prevArcLength = getEllipseArcLength(aMajorMag, aMinorMag, laRadians[-1] - twoPi, laRadians[0]) - n1Limit = elementsCountAroundAtrium if (n3 == 0) else (elementsCountAroundAtrialFreeWall + 1) - for n1 in range(n1Limit): - radiansAround = laRadians[n1] - cosRadiansAround = math.cos(radiansAround) - sinRadiansAround = math.sin(radiansAround) + baseInclineRadians = aBaseBackInclineRadians + side = vector.normalise([ d1[1], -d1[0], 0.0 ]) + d2 = vector.setMagnitude([ (up[c]*math.cos(baseInclineRadians) + side[c]*math.sin(baseInclineRadians)) for c in range(3) ], baseDerivative2Scale) + ltBaseOuterx .append(x) + ltBaseOuterd1.append(d1) + ltBaseOuterd2.append(d2) + # generate point on posterior base septum + xi = 0.85 # GRC fudge factor + baseSeptumPosteriorx = [ 0.0, (1.0 - xi)*ltBaseOuterx[0][1] + xi*ltBaseOuterx[-1][1], 0.0 ] + nx = [ ltBaseOuterx[-1], baseSeptumPosteriorx ] + nd1 = interp.smoothCubicHermiteDerivativesLine(nx, [ ltBaseOuterd1[-1], [ vector.magnitude(ltBaseOuterd1[-1]), 0.0, 0.0 ] ], + fixStartDerivative = True, fixEndDirection = True ) + ltBaseOuterx .append(nx[1]) + # GRC fudge factor: derivative must be lower to fit inlets: + ltBaseOuterd1.append([ 0.35*d for d in nd1[1] ]) + # derivative 2 slopes directly back = no x component + ltBaseOuterd2.append([ 0.0, -baseDerivative2Scale*math.sin(aBaseBackInclineRadians), baseDerivative2Scale*math.cos(aBaseBackInclineRadians) ]) + + # get key points around la, ra to put element boundaries on (converted to arc angle below) + vx, vd1 = interp.sampleCubicHermiteCurves(ltBaseOuterx, ltBaseOuterd1, elementsCountAroundTrackSurface)[0:2] + # laa end + er = 0.5*laaLeft*elementsCountAroundTrackSurface + e = int(er) + xi = er - e + laaEndY = interp.interpolateCubicHermite(vx[e], vd1[e], vx[e + 1], vd1[e + 1], xi)[1] + laaEndRadians = getEllipseRadiansToX(ayOuter, byOuter, laaEndY - laCentreY, sideRadians) + #print('laaEnd y', laaEndY, 'radians', laaEndRadians) + # venous midpoint + er = 0.5*(2.0 - laVenousMidpointLeft)*elementsCountAroundTrackSurface + e = int(er) + xi = er - e + laVenousMidpointX = interp.interpolateCubicHermite(vx[e], vd1[e], vx[e + 1], vd1[e + 1], xi)[0] + laVenousMidpointRadians = getEllipseRadiansToX(axOuter, bxOuter, laVenousMidpointX - laCentreX, backRadians) + #print('laVenousMidpointLeft', laVenousMidpointLeft, 'e', e, 'xi', xi, 'x', laVenousMidpointX, 'radians', laVenousMidpointRadians) + # note ra points these are computed on the left atrium and mirrored at the end + er = 0.5*(2.0 - raVenousRight)*elementsCountAroundTrackSurface + e = int(er) + xi = er - e + raVenousRightX = interp.interpolateCubicHermite(vx[e], vd1[e], vx[e + 1], vd1[e + 1], xi)[0] + raVenousRightRadians = getEllipseRadiansToX(axOuter, bxOuter, raVenousRightX - laCentreX, backRadians) + #print('raVenousRight', raVenousRight, 'e', e, 'xi', xi, 'x', raVenousRightX, 'radians', raVenousRightRadians) + er = 0.5*raaPouchRight*elementsCountAroundTrackSurface + e = int(er) + xi = er - e + raaPouchRightY = interp.interpolateCubicHermite(vx[e], vd1[e], vx[e + 1], vd1[e + 1], xi)[1] + raaPouchRightRadians = getEllipseRadiansToX(ayOuter, byOuter, raaPouchRightY - laCentreY, sideRadians) + #print('raaPouchRight', raaPouchRight, 'e', e, 'xi', xi, 'y', raaPouchRightY, 'radians', raaPouchRightRadians) + + # get numbers of elements and lengths of sections of left atrium (outer) + elementsCountAroundLeftAtriumAorta, elementsCountAroundLeftAtrialAppendageBase, elementsCountAroundLeftAtriumLPV, elementsCountAroundLeftAtriumRPV = \ + getLeftAtriumBaseFreewallElementsCounts(elementsCountAroundLeftAtriumFreeWall) + laaLength = getEllipseArcLength(aBaseOuterMajorMag, aBaseOuterMinorMag, laCfbLeftRadians, laaEndRadians) + laaLeftLength = getEllipseArcLength(aBaseOuterMajorMag, aBaseOuterMinorMag, laaEndRadians, laVenousMidpointRadians) + laVenousRightLength = aRemainingLength - laaLeftLength - laaLength + + # get element lengths/derivatives at edges of each section and transition element sizes between + laaEndDerivative = laVenousMidpointDerivative = laaLeftLength/elementsCountAroundLeftAtriumLPV + laaElementLengths = interp.sampleCubicElementLengths(laaLength, elementsCountAroundLeftAtrialAppendageBase, startDerivative = aCfbSideDerivativeLength, endDerivative = laaEndDerivative) + lvlElementLengths = interp.sampleCubicElementLengths(laaLeftLength, elementsCountAroundLeftAtriumLPV, startDerivative = laaEndDerivative, endDerivative = laVenousMidpointDerivative) + lvrElementLengths = interp.sampleCubicElementLengths(laVenousRightLength, elementsCountAroundLeftAtriumRPV, startDerivative = laVenousMidpointDerivative, endDerivative = atrialSeptumOuterElementLength) + + # get radians of nodes around left atrium, starting at cfb + elementsCountAroundLeftAtrium = elementsCountAroundLeftAtriumFreeWall + elementsCountAroundAtrialSeptum + laRadians = [] + radiansAround = laSeptumAnteriorRadians + for n1 in range(elementsCountAroundLeftAtrium): + laRadians.append(radiansAround) + if n1 == 0: + elementLength = aSeptumCfbSideElementLength + elif n1 < (elementsCountAroundLeftAtriumAorta + elementsCountAroundLeftAtrialAppendageBase): + elementLength = laaElementLengths[n1 - elementsCountAroundLeftAtriumAorta] + elif n1 < (elementsCountAroundLeftAtriumAorta + elementsCountAroundLeftAtrialAppendageBase + elementsCountAroundLeftAtriumLPV): + elementLength = lvlElementLengths[n1 - elementsCountAroundLeftAtriumAorta - elementsCountAroundLeftAtrialAppendageBase] + elif n1 == (elementsCountAroundLeftAtriumFreeWall - 1): + radiansAround = laSeptumPosteriorRadians + 2.0*math.pi + continue + elif n1 < elementsCountAroundLeftAtriumFreeWall: + elementLength = lvrElementLengths[n1 - elementsCountAroundLeftAtriumAorta - elementsCountAroundLeftAtrialAppendageBase - elementsCountAroundLeftAtriumLPV] + else: + radiansAround = updateEllipseAngleByArcLength(aBaseInnerMajorMag, aBaseInnerMinorMag, radiansAround, atrialSeptumInnerElementLength) + continue + radiansAround = updateEllipseAngleByArcLength(aBaseOuterMajorMag, aBaseOuterMinorMag, radiansAround, elementLength) - # get derivative around - if n1 == (elementsCountAroundAtrium - 1): - nextArcLength = finalArcLength - else: - nextArcLength = getEllipseArcLength(aMajorMag, aMinorMag, laRadians[n1], laRadians[n1 + 1]) - if (n1 <= 1) or (n1 > elementsCountAroundAtrialFreeWall): - derivativeAround = min(prevArcLength, nextArcLength) - else: - derivativeAround = max(prevArcLength, nextArcLength) - prevArcLength = nextArcLength - - if (n3 == 1) and (n1 == 0): - x = [ cfbX, cfbY, cfbZ ] - d1 = [ -lvOutletDerivativeAround, 0.0, 0.0 ] - d2 = [ 0.0, baseDerivative2Scale*sinFrontInclineRadians, baseDerivative2Scale*cosFrontInclineRadians ] - elif (n3 == 1) and (n1 == 1): - x = [ cfbLeftX, cfbLeftY, cfbLeftZ ] - d1 = [ -lvOutletDerivativeAround*cosPi_3, lvOutletDerivativeAround*sinPi_3*cosFrontInclineRadians, -lvOutletDerivativeAround*sinPi_3*sinFrontInclineRadians ] - d2 = [ 0.0, baseDerivative2Scale*sinFrontInclineRadians, baseDerivative2Scale*cosFrontInclineRadians ] + #print('la radiansAround start', laSeptumAnteriorRadians, 'end', radiansAround - 2.0*math.pi) + #print('laRadians', laRadians) + + # get numbers of elements and lengths of sections of right atrium (outer) + elementsCountAroundRightAtriumPosteriorVenous, elementsCountAroundRightAtrialAppendagePlainBase, \ + elementsCountAroundRightAtrialAppendagePouchBase, elementsCountAroundRightAtriumAorta \ + = getRightAtriumBaseFreewallElementsCounts(elementsCountAroundRightAtriumFreeWall) + elementsCountAroundRightAtrialAppendageBase = elementsCountAroundRightAtrialAppendagePlainBase + elementsCountAroundRightAtrialAppendagePouchBase + raaPouchLength = getEllipseArcLength(aBaseOuterMajorMag, aBaseOuterMinorMag, laCfbLeftRadians, raaPouchRightRadians) + raaPlainLength = getEllipseArcLength(aBaseOuterMajorMag, aBaseOuterMinorMag, raaPouchRightRadians, raVenousRightRadians) + raPosteriorVenousLength = aRemainingLength - raaPlainLength - raaPouchLength + + # get element lengths/derivatives at edges of each section and transition element sizes between + #raPosteriorVenousLimitDerivative = (raaLength - 0.5*aCfbSideDerivativeLength)/(elementsCountAroundRightAtrialAppendageBase - 0.5) + raPosteriorVenousLimitDerivative = raaPlainDerivative = raaPlainLength/elementsCountAroundRightAtrialAppendagePlainBase + raaPouchElementLengths = interp.sampleCubicElementLengths(raaPouchLength, elementsCountAroundRightAtrialAppendagePouchBase, startDerivative = aCfbSideDerivativeLength, endDerivative = raaPlainDerivative) + raaPlainElementLengths = interp.sampleCubicElementLengths(raaPlainLength, elementsCountAroundRightAtrialAppendagePlainBase, startDerivative = raaPlainDerivative, endDerivative = raPosteriorVenousLimitDerivative) + ravElementLengths = interp.sampleCubicElementLengths(raPosteriorVenousLength, elementsCountAroundRightAtriumPosteriorVenous, startDerivative = raPosteriorVenousLimitDerivative, endDerivative = atrialSeptumOuterElementLength) + + # get radians of nodes around right atrium (computed on left and mirrored at the end), starting at cfb + elementsCountAroundRightAtrium = elementsCountAroundRightAtriumFreeWall + elementsCountAroundAtrialSeptum + raRadians = [] + radiansAround = laSeptumAnteriorRadians + for n1 in range(elementsCountAroundRightAtrium): + raRadians.append(radiansAround) + if n1 == 0: + elementLength = aSeptumCfbSideElementLength + elif n1 < (elementsCountAroundRightAtriumAorta + elementsCountAroundRightAtrialAppendagePouchBase): + elementLength = raaPouchElementLengths[n1 - elementsCountAroundRightAtriumAorta] + elif n1 < (elementsCountAroundRightAtriumAorta + elementsCountAroundRightAtrialAppendageBase): + elementLength = raaPlainElementLengths[n1 - elementsCountAroundRightAtriumAorta - elementsCountAroundRightAtrialAppendagePouchBase] + elif n1 == (elementsCountAroundRightAtriumFreeWall - 1): + radiansAround = laSeptumPosteriorRadians + 2.0*math.pi + continue + elif n1 < elementsCountAroundRightAtriumFreeWall: + elementLength = ravElementLengths[n1 - elementsCountAroundRightAtriumAorta - elementsCountAroundRightAtrialAppendageBase] + else: + radiansAround = updateEllipseAngleByArcLength(aBaseInnerMajorMag, aBaseInnerMinorMag, radiansAround, atrialSeptumInnerElementLength) + continue + radiansAround = updateEllipseAngleByArcLength(aBaseOuterMajorMag, aBaseOuterMinorMag, radiansAround, elementLength) + + #print('ra radiansAround start', laSeptumAnteriorRadians, 'end', radiansAround - 2.0*math.pi) + #print('raRadians', raRadians) + + # get base points on inside and outside of left and right atria + for a in [ 'la', 'ra' ]: + if a == 'la': + aRadians = laRadians + elementsCountAroundAtrium = elementsCountAroundLeftAtrium + elementsCountAroundAtriumFreeWall = elementsCountAroundLeftAtriumFreeWall + else: # a == 'ra': + aRadians = raRadians + elementsCountAroundAtrium = elementsCountAroundRightAtrium + elementsCountAroundAtriumFreeWall = elementsCountAroundRightAtriumFreeWall + aBaseInnerx = [] + aBaseInnerd1 = [] + aBaseInnerd2 = [] + aBaseOuterx = copy.deepcopy(ltBaseOuterx [0:2]) + aBaseOuterd1 = copy.deepcopy(ltBaseOuterd1[0:2]) + aBaseOuterd2 = copy.deepcopy(ltBaseOuterd2[0:2]) + for n3 in range(2): + if n3 == 0: + aMajorMag = aBaseInnerMajorMag + aMinorMag = aBaseInnerMinorMag + z = -aBaseSlopeHeight else: - x = [ - laCentreX + cosRadiansAround*aMajorX + sinRadiansAround*aMinorX, - laCentreY + cosRadiansAround*aMajorY + sinRadiansAround*aMinorY, - z ] - d1x = -sinRadiansAround*aMajorX + cosRadiansAround*aMinorX - d1y = -sinRadiansAround*aMajorY + cosRadiansAround*aMinorY - scale1 = derivativeAround/math.sqrt(d1x*d1x + d1y*d1y) - d1 = [ d1x*scale1, d1y*scale1, 0.0 ] - - if (n3 == 0) or (n1 > elementsCountAroundAtrialFreeWall): + aMajorMag = aBaseOuterMajorMag + aMinorMag = aBaseOuterMinorMag + z = 0.0 + + aMajorX = aMajorMag*cosMajorAxisRadians + aMajorY = aMajorMag*sinMajorAxisRadians + aMinorX = -aMinorMag*sinMajorAxisRadians + aMinorY = aMinorMag*cosMajorAxisRadians + + finalArcLength = prevArcLength = getEllipseArcLength(aMajorMag, aMinorMag, aRadians[-1] - 2.0*math.pi, aRadians[0]) + n1Start = 0 if (n3 == 0) else 2 + n1Limit = elementsCountAroundAtrium if (n3 == 0) else elementsCountAroundAtriumFreeWall + for n1 in range(n1Start, n1Limit): + radiansAround = aRadians[n1] + cosRadiansAround = math.cos(radiansAround) + sinRadiansAround = math.sin(radiansAround) + + # get derivative around + if n1 == (elementsCountAroundAtrium - 1): + nextArcLength = finalArcLength + else: + nextArcLength = getEllipseArcLength(aMajorMag, aMinorMag, aRadians[n1], aRadians[n1 + 1]) + # GRC review; can use exact values on outer. Compare? + derivativeLength = 0.5*(prevArcLength + nextArcLength) + prevArcLength = nextArcLength + + x = [ laCentreX + cosRadiansAround*aMajorX + sinRadiansAround*aMinorX, + laCentreY + cosRadiansAround*aMajorY + sinRadiansAround*aMinorY, + z ] + d1 = vector.setMagnitude([ -sinRadiansAround*aMajorX + cosRadiansAround*aMinorX, + -sinRadiansAround*aMajorY + cosRadiansAround*aMinorY, + 0.0 ], derivativeLength) + if (n1 < 1) or (n1 >= elementsCountAroundAtriumFreeWall): d2 = [ 0.0, 0.0, baseDerivative2Scale ] # calculated later - elif (n1 == elementsCountAroundAtrialFreeWall): - # get collapsed crux (outer back base septum) position and derivative: - xi = 0.9 # GRC fudge factor - nx = [ laBaseOuterx [-1], [ 0.0, (1.0 - xi)*laBaseOuterx[0][1] + xi*laBaseOuterx[-1][1], 0.0 ] ] - nd1 = interp.smoothCubicHermiteDerivativesLine(nx, [ laBaseOuterd1[-1], [ vector.magnitude(laBaseOuterd1[-1]), 0.0, 0.0 ] ], - fixStartDerivative = True, fixEndDirection = True ) - x = nx[1] - # GRC fudge factor: derivative must be lower to fit inlets: - d1 = [ 0.35*d for d in nd1[1] ] - # derivative 2 slopes directly back = no x component - d2 = [ 0.0, -baseDerivative2Scale*math.sin(aBaseBackInclineRadians), baseDerivative2Scale*math.cos(aBaseBackInclineRadians) ] else: - baseInclineRadians = 0.0 - sideRadians = laSeptumRadians + math.pi - backRadians = sideRadians + math.pi*0.5 if radiansAround < sideRadians: - xi = (radiansAround - laCfbLeftRadians)/sideRadians + xi = (radiansAround - laCfbLeftRadians)/(sideRadians - laCfbLeftRadians) baseInclineRadians = (1.0 - xi)*aBaseFrontInclineRadians + xi*aBaseSideInclineRadians elif radiansAround < backRadians: - xi = (radiansAround - sideRadians)/(math.pi*0.5) + xi = (radiansAround - sideRadians)/(0.5*math.pi) baseInclineRadians = (1.0 - xi)*aBaseSideInclineRadians + xi*aBaseBackInclineRadians else: baseInclineRadians = aBaseBackInclineRadians - scale2 = baseDerivative2Scale/vector.magnitude(d1) - side = [ d1[1]*scale2, -d1[0]*scale2, 0.0 ] - up = [ 0.0, 0.0, baseDerivative2Scale ] - d2 = [ (up[c]*math.cos(baseInclineRadians) + side[c]*math.sin(baseInclineRadians)) for c in range(3) ] - - if n3 == 0: - laBaseInnerx.append(x) - laBaseInnerd1.append(d1) - laBaseInnerd2.append(d2) + side = vector.normalise([ d1[1], -d1[0], 0.0 ]) + d2 = vector.setMagnitude([ (up[c]*math.cos(baseInclineRadians) + side[c]*math.sin(baseInclineRadians)) for c in range(3) ], baseDerivative2Scale) + if n3 == 0: + aBaseInnerx.append(x) + aBaseInnerd1.append(d1) + aBaseInnerd2.append(d2) + else: + aBaseOuterx.append(x) + aBaseOuterd1.append(d1) + aBaseOuterd2.append(d2) + + aBaseOuterx .append(ltBaseOuterx [-1]) + aBaseOuterd1.append(ltBaseOuterd1[-1]) + aBaseOuterd2.append(ltBaseOuterd2[-1]) + for n1 in range(elementsCountAroundAtriumFreeWall + 1, elementsCountAroundAtrium): + aBaseOuterx .append(None) + aBaseOuterd1.append(None) + aBaseOuterd2.append(None) + + # calculate d3 from difference across wall + aBaseInnerd3 = [] + aBaseOuterd3 = [] + for n1 in range(elementsCountAroundAtrium): + if aBaseOuterx[n1]: + d3 = [ (aBaseOuterx[n1][c] - aBaseInnerx[n1][c]) for c in range(3) ] else: - laBaseOuterx.append(x) - laBaseOuterd1.append(d1) - laBaseOuterd2.append(d2) - - for n1 in range(elementsCountAroundAtrialFreeWall + 1, elementsCountAroundAtrium): - laBaseOuterx.append(None) - laBaseOuterd1.append(None) - laBaseOuterd2.append(None) - - return [ laCentreX, laCentreY, laCentreZ ], laSeptumRadians, laBaseInnerx, laBaseInnerd1, laBaseInnerd2, laBaseOuterx, laBaseOuterd1, laBaseOuterd2 + d3 = [ -2.0*aBaseInnerx[n1][0], 0.0, 0.0 ] + aBaseInnerd3.append(d3) + aBaseOuterd3.append(copy.deepcopy(d3)) + # fix outer d3 on cfb and crux + aBaseOuterd3[0][0] = 0.0 + aBaseOuterd3[elementsCountAroundAtriumFreeWall][0] = 0.0 + + if a == 'la': + laBasex = [ aBaseInnerx , aBaseOuterx ] + laBased1 = [ aBaseInnerd1, aBaseOuterd1 ] + laBased2 = [ aBaseInnerd2, aBaseOuterd2 ] + laBased3 = [ aBaseInnerd3, aBaseOuterd3 ] + else: # a == 'ra': + raBasex = [ aBaseInnerx , aBaseOuterx ] + raBased1 = [ aBaseInnerd1, aBaseOuterd1 ] + raBased2 = [ aBaseInnerd2, aBaseOuterd2 ] + raBased3 = [ aBaseInnerd3, aBaseOuterd3 ] + # reverse and mirror about x == 0 + # reverse all x components, but only y, z components of d1 as winds in opposite direction + for li in (raBasex + raBased2 + raBased3): + for n1 in range(elementsCountAroundAtrium): + if li[n1]: + li[n1][0] = -li[n1][0] + for li in raBased1: + for n1 in range(elementsCountAroundAtrium): + if li[n1]: + li[n1][1] = -li[n1][1] + li[n1][2] = -li[n1][2] + for li in (raBasex + raBased1 + raBased2 + raBased3): + li.reverse() + for n1 in range(elementsCountAroundAtrialSeptum - 1): + li.append(li.pop(0)) + + return laBasex, laBased1, laBased2, laBased3, raBasex, raBased1, raBased2, raBased3, \ + ltBaseOuterx, ltBaseOuterd1, ltBaseOuterd2, \ + [ aSeptumBaseCentreX, aSeptumBaseCentreY, aSeptumBaseCentreZ ], [ laCentreX, laCentreY, laCentreZ ], laSeptumRadians + + +def getAtriumTrackSurface(elementsCountAroundTrackSurface, elementsCountAcrossTrackSurface, + laBaseOuterx, laBaseOuterd1, laBaseOuterd2, aSeptumBaseCentre, aOuterHeight, aOuterSeptumHeight, iaGrooveDerivative): + ''' + Create a TrackSurface covering the outer surface of the left atrium on which + inlets will be placed. Elements vary fastest from posterior to anterior, then from septum to outer left. + :param laBaseOuterx, laBaseOuterd1, laBaseOuterd2: coordinates, derivatives and transverse derivatives + around left atrium outside wall from cfb to crux. + :return: TrackSurface + ''' + # resample to get equal spaced elements around + elementsCountAlongTrackSurface = elementsCountAroundTrackSurface//2 + vx, vd1, ve, vxi = interp.sampleCubicHermiteCurves(laBaseOuterx, laBaseOuterd1, elementsCountAroundTrackSurface)[0:4] + # GRC these are not necessarily orthogonal to d1 any more: fix? + vd2 = interp.interpolateSampleLinear(laBaseOuterd2, ve, vxi) + + # get la ridge points from cubic functions from ax = septum groove centre through cx on peak to dx on mid outer LV base + ax = [ 0.0, aSeptumBaseCentre[1], aOuterSeptumHeight ] + ad1 = [ -iaGrooveDerivative, 0.0, 0.0 ] + dx = vx[elementsCountAlongTrackSurface] + dd1 = [ -d for d in vd2[elementsCountAlongTrackSurface]] + # fudge factor + px, pd1 = interp.sampleCubicHermiteCurves([ ax, dx ], [ ad1, dd1 ], elementsCountOut = 2, lengthFractionStart = 0.4, arcLengthDerivatives = True)[0:2] + nx = [ ax, [ px[1][0], px[1][1], aOuterHeight ] ] + nd1 = interp.smoothCubicHermiteDerivativesLine(nx, [ ad1, [ pd1[1][0], pd1[1][1], 0.0 ] ], fixStartDerivative = True, fixEndDirection = True) + cx = nx[1] + cd1 = nd1[1] + # bx = in-between point to get more curvature near septum + xi = 0.4 + bx = interp.interpolateCubicHermite(ax, ad1, cx, cd1, xi) + bd1 = interp.interpolateCubicHermiteDerivative(ax, ad1, cx, cd1, xi) + rx, rd1 = interp.sampleCubicHermiteCurves([ ax, bx, cx, dx ], [ ad1, bd1, cd1, dd1 ], elementsCountOut = elementsCountAlongTrackSurface, arcLengthDerivatives = True)[0:2] + + # get track surface points on arcs from posterior on septum end, to anterior on outer left + nx = [] + nd1 = [] + nd2 = [] + for na in range(elementsCountAlongTrackSurface): + np = -1 - na + # sample arch from double cubic from posterior through ridge to anterior + ax = [ vx[np], rx[na], vx[na] ] + ad1 = [ vd2[np], [ rd1[na][1], -rd1[na][0], 0.0 ], [ -d for d in vd2[na]] ] + ad1 = interp.smoothCubicHermiteDerivativesLine(ax, ad1, fixStartDirection = True, fixEndDirection = True) + lx, ld1, le, lxi = interp.sampleCubicHermiteCurves(ax, ad1, elementsCountAcrossTrackSurface)[0:4] + ld2 = interp.interpolateSampleLinear([ [ -d for d in vd1[np] ], rd1[na], vd1[na] ], le, lxi) + nx += lx + nd1 += ld1 + nd2 += ld2 # to be smoothed when all rows assembled. + # add last point at central outer point of atrium, d1 all zero, d2 rotating around + d1 = vd1[elementsCountAlongTrackSurface] + d2 = vector.setMagnitude(vd2[elementsCountAlongTrackSurface], vector.magnitude(d1)) + for n1 in range(elementsCountAcrossTrackSurface + 1): + nx.append(copy.deepcopy(vx[elementsCountAlongTrackSurface])) + nd1.append([ 0.0, 0.0, 0.0 ]) + radiansAround = math.pi*n1/elementsCountAcrossTrackSurface + wt1 = -math.cos(radiansAround) + wt2 = -math.sin(radiansAround) + nd2.append([ (wt1*d1[c] + wt2*d2[c]) for c in range(3) ]) + + # smooth nd2 around each side, away from base edges + for n1 in range(1, elementsCountAcrossTrackSurface): + sx = [] + sd2 = [] + for n2 in range(elementsCountAlongTrackSurface + 1): + n = n2*(elementsCountAcrossTrackSurface + 1) + n1 + sx.append(nx[n]) + sd2.append(nd2[n]) + sd2 = interp.smoothCubicHermiteDerivativesLine(sx, sd2, fixStartDirection = True, fixEndDirection = True) + for n2 in range(elementsCountAcrossTrackSurface + 1): + n = n2*(elementsCountAcrossTrackSurface + 1) + n1 + nd2[n] = sd2[n2] + + return TrackSurface(elementsCountAcrossTrackSurface, elementsCountAlongTrackSurface, nx, nd1, nd2) + + +def getAtrialAppendageWedgePoints(basex, based1, based2, based3, angle1radians, angle2radians, angle3Radians, baseLength, + elementsCountAroundAppendage, elementsCountRadial, arcLength, arcRadius, wallThickness, wedgeAngleRadians): + ''' + Get points on wedge midline at end of atrial appendage. + :param basex, based1, based2, based3: Base point on atrium to project from. + :param angle1radians, angle2radians: Rotation of direction toward d1, d2. + :param baseLength: Distance to project out from base. + :param elementsCountAcrossWedge: Elements around arc, one less than numbers of points out. + :param elementsCountAlongAppendage: Number of radial elements along appendage, used to + determine end derivatives. + :param arcLength: Length of outer arc around atrial appendage wedge. + :param arcRadius: Radius of outer arc. Must be < pi + :param wallThickness: Atrial appendage wall thickness + :param wedgeAngleRadians: Angle from base to top at end of wedge. + :return: aawx[n3][n1], aawd1[n3][n1], aawd2[n3][n1], aawd3[n3][n1], + elementsCountAcrossWedge, wedgePointsMap, wedgeDerivativesMap. + where: + n3 is range 2 inner, outer; n1 is 0-elementsCountAcrossWedge + wedgePointsMap maps points count around appendage --> node index across wedge + derivativesMap is for passing to createAnnulusMesh3d. + n is number of points around arc. + ''' + elementsCountAcrossWedge = (elementsCountAroundAppendage - 4)//2 + # wedge centre: + wcx, wd1, wd2, wd3 = getCircleProjectionAxes(basex, based1, based2, based3, baseLength, angle1radians, angle2radians, angle3Radians) + # arc centre: + acx = [ (wcx[c] - arcRadius*wd3[c]) for c in range(3) ] + wedgeLength = wallThickness/math.tan(0.5*wedgeAngleRadians) + cosHalfWedgeAngleRadians = math.cos(0.5*wedgeAngleRadians) + sinHalfWedgeAngleRadians = math.sin(0.5*wedgeAngleRadians) + aawx = [ [], [] ] + aawd1 = [ [], [] ] + aawd2 = [ [], [] ] + aawd3 = [ [], [] ] + arcRadians = arcLength/arcRadius + elementLengthOuterRadial = baseLength/elementsCountRadial + for n2 in range(2): + if n2 == 0: + radius = arcRadius - wedgeLength + derivativeMag = elementLengthOuterRadial - wedgeLength + else: + radius = arcRadius + derivativeMag = elementLengthOuterRadial + elementLengthArc = (arcLength/elementsCountAcrossWedge)*(radius/arcRadius) + for n1 in range(0, elementsCountAcrossWedge + 1): + angleRadians = arcRadians*(n1/elementsCountAcrossWedge - 0.5) + cosAngleRadians = math.cos(angleRadians) + sinAngleRadians = math.sin(angleRadians) + x = [ (acx[c] + radius*(cosAngleRadians*wd3[c] + sinAngleRadians*wd1[c])) for c in range(3) ] + d1 = [ elementLengthArc*(cosAngleRadians*wd1[c] - sinAngleRadians*wd3[c]) for c in range(3) ] + d2 = [ derivativeMag*( cosHalfWedgeAngleRadians*(-sinAngleRadians*wd1[c] - cosAngleRadians*wd3[c]) + sinHalfWedgeAngleRadians*wd2[c]) for c in range(3) ] + d3 = [ derivativeMag*(-cosHalfWedgeAngleRadians*(-sinAngleRadians*wd1[c] - cosAngleRadians*wd3[c]) + sinHalfWedgeAngleRadians*wd2[c]) for c in range(3) ] + #d3 = [ derivativeMag*(cosAngleRadians*wd3[c] + sinAngleRadians*wd1[c]) for c in range(3) ] # flat d3, if using full WedgeAngleRadians + aawx [n2].append(x ) + aawd1[n2].append(d1) + aawd2[n2].append(d2) + aawd3[n2].append(d3) + + wedgePointsMap = [ 0 ]*3 + list(range(1, elementsCountAcrossWedge)) + [ elementsCountAcrossWedge ]*3 + list(range(elementsCountAcrossWedge - 1, 0, -1)) + wedgeDerivativesMap = ( [ ( ( -1, 0, 0 ), ( 0, -1, 0 ), None, ( 0, 0, 0 ) ), + ( ( 0, 0, 0 ), ( +1, 0, 0 ), None, ( 0, 0, 0 ) ), # inside corner, collapsed + ( ( 0, 0, 0 ), ( 0, 0, +1 ), None, None ) ] + + [ ( None , ( 0, 0, +1 ), None ) ]*(elementsCountAcrossWedge - 1) # bottom + + [ ( None , ( 0, 0, +1 ), None, ( 0, 0, 0 ) ), + ( ( 0, 0, 0 ), ( -1, 0, 0 ), None, ( 0, 0, 0 ) ), # inside corner, collapsed + ( ( 0, 0, 0 ), ( 0, -1, 0 ), None, ( -1, 0, 0 ) ) ] + + [ ( ( -1, 0, 0 ), ( 0, -1, 0 ), None ) ]*(elementsCountAcrossWedge - 1) ) # top + if (elementsCountAroundAppendage%2) == 1: + wedgePointsMap.insert(1, 0) + wedgeDerivativesMap.insert(1, wedgeDerivativesMap[1]) + #print('wedgePointsMap',wedgePointsMap) + #print('wedgeDerivativesMap',wedgeDerivativesMap) + return aawx, aawd1, aawd2, aawd3, elementsCountAcrossWedge, wedgePointsMap, [ wedgeDerivativesMap, wedgeDerivativesMap ] diff --git a/scaffoldmaker/meshtypes/meshtype_3d_heartventricles1.py b/scaffoldmaker/meshtypes/meshtype_3d_heartventricles1.py index c7caa6f1..e3ca3d76 100644 --- a/scaffoldmaker/meshtypes/meshtype_3d_heartventricles1.py +++ b/scaffoldmaker/meshtypes/meshtype_3d_heartventricles1.py @@ -43,53 +43,76 @@ def getParameterSetNames(): @staticmethod def getDefaultOptions(parameterSetName='Default'): - options = { - 'Number of elements around LV free wall' : 5, - 'Number of elements around RV free wall' : 7, - 'Number of elements up LV apex' : 1, - 'Number of elements up RV' : 4, - 'Unit scale' : 1.0, - 'Interventricular sulcus derivative factor' : 0.5, - 'LV outer height' : 1.0, - 'LV outer diameter' : 1.0, - 'LV free wall thickness' : 0.12, - 'LV apex thickness' : 0.06, - 'RV inner height fraction' : 0.85, - 'RV arc around degrees' : 155.0, - 'RV arc apex fraction' : 0.6, - 'RV free wall thickness' : 0.05, - 'RV width' : 0.4, - 'RV width growth factor' : 0.7, - 'RV side extension' : 0.12, - 'RV side extension growth factor' : 0.5, - 'Ventricular septum thickness' : 0.1, - 'Ventricular septum base radial displacement' : 0.05, - 'Use cross derivatives' : False, - 'Refine' : False, - 'Refine number of elements surface' : 4, - 'Refine number of elements through LV wall' : 1, - 'Refine number of elements through wall' : 1 - } - if 'Human' in parameterSetName: + isHuman = 'Human' in parameterSetName + isMouse = 'Mouse' in parameterSetName + isPig = 'Pig' in parameterSetName + isRat = 'Rat' in parameterSetName + notUnitScale = 'Unit' not in parameterSetName + options = {} + options['Number of elements around LV free wall'] = 5 + options['Number of elements around RV free wall'] = 7 + options['Number of elements up LV apex'] = 1 + options['Number of elements up RV'] = 3 + options['Unit scale'] = 1.0 + options['Interventricular sulcus derivative factor'] = 0.5 + options['LV outer height'] = 1.0 + options['LV outer diameter'] = 0.95 + options['LV free wall thickness'] = 0.14 + options['LV apex thickness'] = 0.08 + options['RV inner height fraction'] = 0.85 + options['RV arc around degrees'] = 145.0 + options['RV arc apex fraction'] = 0.5 + options['RV free wall thickness'] = 0.05 + options['RV width'] = 0.3 + options['RV width growth factor'] = 0.7 + options['RV side extension'] = 0.12 + options['RV side extension growth factor'] = 0.5 + options['Ventricular septum thickness'] = 0.12 + options['Ventricular septum base radial displacement'] = 0.1 + options['Use cross derivatives'] = False + options['Refine'] = False + options['Refine number of elements surface'] = 4 + options['Refine number of elements through LV wall'] = 1 + options['Refine number of elements through wall'] = 1 + if isHuman: if 'Unit' not in parameterSetName: options['Unit scale'] = 80.0 - options['LV outer height'] = 0.9 - elif 'Mouse' in parameterSetName: - if 'Unit' not in parameterSetName: - options['Unit scale'] = 5.0 + elif isMouse or isRat: + if notUnitScale: + options['Unit scale'] = 5.0 if isMouse else 12.0 + #options['Interventricular sulcus derivative factor'] = 0.5 + options['LV outer height'] = 0.95 + options['LV outer diameter'] = 0.95 + options['LV free wall thickness'] = 0.15 options['LV apex thickness'] = 0.08 - options['RV width'] = 0.35 - elif 'Pig' in parameterSetName: - options['Number of elements up LV apex'] = 2 + if isMouse: + options['RV inner height fraction'] = 0.9 + else: + options['RV inner height fraction'] = 0.85 + options['RV arc around degrees'] = 140.0 + #options['RV arc apex fraction'] = 0.6 + options['RV free wall thickness'] = 0.07 + options['RV width'] = 0.25 + options['RV width growth factor'] = 0.8 + options['RV side extension'] = 0.05 + options['RV side extension growth factor'] = 0.8 + options['Ventricular septum thickness'] = 0.12 + options['Ventricular septum base radial displacement'] = 0.08 + elif isPig: + options['Number of elements up LV apex'] = 1 options['Number of elements up RV'] = 3 if 'Unit' not in parameterSetName: options['Unit scale'] = 80.0 options['LV outer height'] = 0.9 - options['LV free wall thickness'] = 0.15 + options['LV free wall thickness'] = 0.17 options['LV apex thickness'] = 0.07 options['RV inner height fraction'] = 0.65 - options['RV width growth factor'] = 0.65 - options['Ventricular septum thickness'] = 0.12 + options['RV arc around degrees'] = 140.0 + options['RV free wall thickness'] = 0.06 + options['RV width growth factor'] = 0.4 + options['RV side extension'] = 0.1 + options['RV side extension growth factor'] = 0.4 + options['Ventricular septum thickness'] = 0.13 elif 'Rat' in parameterSetName: if 'Unit' not in parameterSetName: options['Unit scale'] = 12.0 diff --git a/scaffoldmaker/meshtypes/meshtype_3d_heartventriclesbase1.py b/scaffoldmaker/meshtypes/meshtype_3d_heartventriclesbase1.py index 10ffc65a..ff809475 100644 --- a/scaffoldmaker/meshtypes/meshtype_3d_heartventriclesbase1.py +++ b/scaffoldmaker/meshtypes/meshtype_3d_heartventriclesbase1.py @@ -5,9 +5,10 @@ """ from __future__ import division +import copy import math from scaffoldmaker.annotation.annotationgroup import AnnotationGroup, findAnnotationGroupByName -from scaffoldmaker.meshtypes.meshtype_3d_heartatria1 import getLeftAtriumBasePoints +from scaffoldmaker.meshtypes.meshtype_3d_heartatria1 import MeshType_3d_heartatria1, getAtriumBasePoints from scaffoldmaker.meshtypes.meshtype_3d_heartventricles1 import MeshType_3d_heartventricles1 from scaffoldmaker.meshtypes.scaffold_base import Scaffold_base from scaffoldmaker.utils.eft_utils import remapEftLocalNodes, remapEftNodeValueLabel, scaleEftNodeValueLabels, setEftScaleFactorIds @@ -49,57 +50,82 @@ def getParameterSetNames(): @staticmethod def getDefaultOptions(parameterSetName='Default'): options = MeshType_3d_heartventricles1.getDefaultOptions(parameterSetName) + atriaOptions = MeshType_3d_heartatria1.getDefaultOptions(parameterSetName) + isHuman = 'Human' in parameterSetName + isMouse = 'Mouse' in parameterSetName + isPig = 'Pig' in parameterSetName + isRat = 'Rat' in parameterSetName + notUnitScale = 'Unit' not in parameterSetName # only works with particular numbers of elements around - options['Number of elements around LV free wall'] = 5 + options['Number of elements around LV free wall'] = 7 options['Number of elements around RV free wall'] = 7 - options['Number of elements around atrial free wall'] = 6 options['Number of elements around atrial septum'] = 3 + options['Number of elements around left atrium free wall'] = 8 + options['Number of elements around right atrium free wall'] = 6 # reduce LV outer height from default as adding to it options['LV outer height'] = 0.9 # additional options - options['Atria base inner major axis length'] = 0.55 - options['Atria base inner minor axis length'] = 0.45 - options['Atria major axis rotation degrees'] = 40.0 - options['Atrial septum thickness'] = 0.07 - options['Atrial base wall thickness'] = 0.07 - options['Atrial base slope degrees'] = 30.0 - options['Base height'] = 0.15 - options['Base thickness'] = 0.06 + options['Base height'] = 0.16 + options['Base thickness'] = 0.08 options['Fibrous ring thickness'] = 0.005 - options['LV outlet front incline degrees'] = 15.0 - options['LV outlet inner diameter'] = 0.3 - options['LV outlet wall thickness'] = 0.025 + options['LV outlet front incline degrees'] = atriaOptions['Atrial base front incline degrees'] # same for now + options['LV outlet inner diameter'] = 0.28 + options['LV outlet wall thickness'] = 0.022 options['RV outlet left incline degrees'] = 30.0 - options['RV outlet inner diameter'] = 0.27 - options['RV outlet wall thickness'] = 0.025 + options['RV outlet inner diameter'] = 0.26 + options['RV outlet wall thickness'] = 0.02 options['Ventricles outlet element length'] = 0.1 options['Ventricles outlet spacing y'] = 0.02 options['Ventricles outlet spacing z'] = 0.1 - options['Ventricles rotation degrees'] = 16.0 - options['Ventricles translation x'] = -0.19 + options['Ventricles rotation degrees'] = 18.0 + options['Ventricles translation x'] = -0.16 options['Ventricles translation y'] = -0.2 - if 'Human' in parameterSetName: - pass - elif 'Mouse' in parameterSetName: - options['LV outer height'] = 1.0 - elif 'Pig' in parameterSetName: + for key in [ + 'Atria base inner major axis length', + 'Atria base inner minor axis length', + 'Atria major axis rotation degrees', + 'Atrial septum length', + 'Atrial septum thickness', + 'Atrial base slope degrees', + 'Atrial base wall thickness', + 'Left atrium venous midpoint left', + 'Right atrium venous right', + 'Left atrial appendage left', + 'Right atrial appendage pouch right']: + options[key] = atriaOptions[key] + if isHuman: + options['LV outer height'] = 0.9 + elif isMouse or isRat: + options['LV outer height'] = 0.85 + options['Base height'] = 0.18 + options['Base thickness'] = 0.08 + options['Fibrous ring thickness'] = 0.005 + options['LV outlet inner diameter'] = 0.23 if isRat else 0.21 + options['LV outlet wall thickness'] = 0.018 if isRat else 0.016 + options['RV outlet left incline degrees'] = 20.0 + options['RV outlet inner diameter'] = 0.23 if isRat else 0.21 + options['RV outlet wall thickness'] = 0.018 if isRat else 0.016 + options['Ventricles outlet element length'] = 0.1 + options['Ventricles outlet spacing y'] = 0.01 + options['Ventricles outlet spacing z'] = 0.1 + options['Ventricles rotation degrees'] = 22.0 + options['Ventricles translation x'] = -0.14 + options['Ventricles translation y'] = -0.18 + elif isPig: + options['LV outer height'] = 0.9 options['RV outlet left incline degrees'] = 10.0 - elif 'Rat' in parameterSetName: - pass + options['Ventricles rotation degrees'] = 19.0 + options['Ventricles translation x'] = -0.16 + options['Ventricles translation y'] = -0.18 return options @staticmethod def getOrderedOptionNames(): optionNames = MeshType_3d_heartventricles1.getOrderedOptionNames() - optionNames.insert(4, 'Number of elements around atrial free wall') - optionNames.insert(5, 'Number of elements around atrial septum') + optionNames.insert(4, 'Number of elements around atrial septum') + optionNames.insert(5, 'Number of elements around left atrium free wall') + optionNames.insert(6, 'Number of elements around right atrium free wall') optionNames += [ - 'Atria base inner major axis length', - 'Atria base inner minor axis length', - 'Atria major axis rotation degrees', - 'Atrial septum thickness', - 'Atrial base wall thickness', - 'Atrial base slope degrees', 'Base height', 'Base thickness', 'Fibrous ring thickness', @@ -114,7 +140,18 @@ def getOrderedOptionNames(): 'Ventricles outlet spacing z', 'Ventricles rotation degrees', 'Ventricles translation x', - 'Ventricles translation y'] + 'Ventricles translation y', + 'Atria base inner major axis length', + 'Atria base inner minor axis length', + 'Atria major axis rotation degrees', + 'Atrial septum length', + 'Atrial septum thickness', + 'Atrial base wall thickness', + 'Atrial base slope degrees', + 'Left atrium venous midpoint left', + 'Right atrium venous right', + 'Left atrial appendage left', + 'Right atrial appendage pouch right'] # want refinement options last for optionName in [ 'Refine', @@ -136,22 +173,30 @@ def checkOptions(options): dependentChanges = MeshType_3d_heartventricles1.checkOptions(options) # only works with particular numbers of elements around options['Number of elements around RV free wall'] = 7 - # Supports only 6 or 8 elements around atrial free wall: - if options['Number of elements around atrial free wall'] <= 6: - options['Number of elements around atrial free wall'] = 6 - requiredElementsCountAroundLVFreeWall = 5 + options['Number of elements around atrial septum'] = 3 + # start with limitations from atria1: + #if options['Number of elements around atrial septum'] < 2: + # options['Number of elements around atrial septum'] = 2 + for key in [ + 'Number of elements around left atrium free wall', + 'Number of elements around right atrium free wall']: + if options[key] < 6: + options[key] = 6 + elif options[key] > 10: + options[key] = 10 + # Supports only 6 or 8 elements around right atrium free wall: + if options['Number of elements around right atrium free wall'] <= 6: + options['Number of elements around right atrium free wall'] = 6 else: - options['Number of elements around atrial free wall'] = 8 - requiredElementsCountAroundLVFreeWall = 7 + options['Number of elements around right atrium free wall'] = 8 + requiredElementsCountAroundLVFreeWall = options['Number of elements around left atrium free wall'] - 1 if options['Number of elements around LV free wall'] != requiredElementsCountAroundLVFreeWall: options['Number of elements around LV free wall'] = requiredElementsCountAroundLVFreeWall dependentChanges = True - #if options['Number of elements around atrial septum'] < 2: - # options['Number of elements around atrial septum'] = 2 - options['Number of elements around atrial septum'] = 3 for key in [ 'Atria base inner major axis length', 'Atria base inner minor axis length', + 'Atrial septum length', 'Atrial septum thickness', 'Atrial base wall thickness', 'Atrial base slope degrees', @@ -171,6 +216,20 @@ def checkOptions(options): options['Atria major axis rotation degrees'] = -75.0 elif options['Atria major axis rotation degrees'] > 75.0: options['Atria major axis rotation degrees'] = 75.0 + for key in [ + 'Left atrium venous midpoint left', + 'Right atrium venous right', + 'Left atrial appendage left']: + if options[key] < 0.0: + options[key] = 0.0 + elif options[key] > 1.0: + options[key] = 1.0 + for key in [ + 'Right atrial appendage pouch right']: + if options[key] < 0.0: + options[key] = 0.0 + elif options[key] > 2.0: + options[key] = 2.0 return dependentChanges @classmethod @@ -189,18 +248,25 @@ def generateBaseMesh(cls, region, options): elementsCountUpLVApex = options['Number of elements up LV apex'] elementsCountUpRV = options['Number of elements up RV'] elementsCountUpLV = elementsCountUpLVApex + elementsCountUpRV - elementsCountAroundAtrialFreeWall = options['Number of elements around atrial free wall'] elementsCountAroundAtrialSeptum = options['Number of elements around atrial septum'] - elementsCountAroundAtria = elementsCountAroundAtrialFreeWall + elementsCountAroundAtrialSeptum + elementsCountAroundLeftAtriumFreeWall = options['Number of elements around left atrium free wall'] + elementsCountAroundLeftAtrium = elementsCountAroundLeftAtriumFreeWall + elementsCountAroundAtrialSeptum + elementsCountAroundRightAtriumFreeWall = options['Number of elements around right atrium free wall'] + elementsCountAroundRightAtrium = elementsCountAroundRightAtriumFreeWall + elementsCountAroundAtrialSeptum # from heartventricles1 and heartatria1: unitScale = options['Unit scale'] # from heartatria1: aBaseInnerMajorMag = unitScale*0.5*options['Atria base inner major axis length'] aBaseInnerMinorMag = unitScale*0.5*options['Atria base inner minor axis length'] aMajorAxisRadians = math.radians(options['Atria major axis rotation degrees']) + aSeptumLength = unitScale*options['Atrial septum length'] aSeptumThickness = unitScale*options['Atrial septum thickness'] aBaseWallThickness = unitScale*options['Atrial base wall thickness'] aBaseSlopeRadians = math.radians(options['Atrial base slope degrees']) + laVenousMidpointLeft = options['Left atrium venous midpoint left'] + raVenousRight = options['Right atrium venous right'] + laaLeft = options['Left atrial appendage left'] + raaPouchRight = options['Right atrial appendage pouch right'] # new: baseHeight = unitScale*options['Base height'] baseThickness = unitScale*options['Base thickness'] @@ -375,96 +441,67 @@ def generateBaseMesh(cls, region, options): n1 = elementsCountAroundOutlet//2 lvOutletOuterd3[n1] = interp.interpolateLagrangeHermiteDerivative(lvOutletOuterx[n1], rvOutletOuterx[0], rvOutletd2, 0.0) - # Left av fibrous ring points + # Get av fibrous ring points, left and right aBaseSlopeHeight = aBaseWallThickness*math.sin(aBaseSlopeRadians) aBaseSlopeLength = aBaseWallThickness*math.cos(aBaseSlopeRadians) - aortaOuterRadius = lvOutletOuterRadius + aortaOuterPlusRadius = lvOutletOuterRadius aBaseFrontInclineRadians = lvOutletFrontInclineRadians - laCentre, laSeptumRadians, laBaseInnerx, laBaseInnerd1, laBaseInnerd2, laBaseOuterx, laBaseOuterd1, laBaseOuterd2 = \ - getLeftAtriumBasePoints(elementsCountAroundAtrialFreeWall, elementsCountAroundAtrialSeptum, + elementsCountAroundTrackSurface = 20 # must be even, twice number of elements along + aBaseSideInclineRadians = 0.0 + aBaseBackInclineRadians = 0.0 + labx, labd1, labd2, labd3, rabx, rabd1, rabd2, rabd3, _, _, _, aSeptumBaseCentre, laCentre, laSeptumRadians, = \ + getAtriumBasePoints(elementsCountAroundAtrialSeptum, elementsCountAroundLeftAtriumFreeWall, elementsCountAroundRightAtriumFreeWall, aBaseInnerMajorMag, aBaseInnerMinorMag, aMajorAxisRadians, - aBaseWallThickness, aBaseSlopeHeight, aBaseSlopeLength, aSeptumThickness, - aortaOuterRadius, aBaseFrontInclineRadians, aBaseSideInclineRadians = 0.0, aBaseBackInclineRadians = 0.0) + aBaseWallThickness, aBaseSlopeHeight, aBaseSlopeLength, aSeptumLength, aSeptumThickness, + aortaOuterPlusRadius, aBaseFrontInclineRadians, aBaseSideInclineRadians, aBaseBackInclineRadians, + laaLeft, laVenousMidpointLeft, raVenousRight, raaPouchRight, elementsCountAroundTrackSurface) laCentre[2] -= (aBaseSlopeHeight + fibrousRingThickness) - # get d3 from inner-outer difference - laBaseInnerd3 = [] - laBaseOuterd3 = [] - for n1 in range(elementsCountAroundAtria): - if laBaseOuterx[n1]: - innerd3 = outerd3 = [ (laBaseOuterx[n1][c] - laBaseInnerx[n1][c]) for c in range(3) ] - else: # septum - innerd3 = [ -2.0*laBaseInnerx[n1][0], 0.0, 0.0 ] - outerd3 = None - laBaseInnerd3.append(innerd3) - laBaseOuterd3.append(outerd3) - # fix cfb, crux centre derivative 3, code duplicated from atria1: - for n1 in [ 0, elementsCountAroundAtrialFreeWall ]: - laBaseOuterd3[n1] = [ 0.0, laBaseOuterx[n1][1] - laBaseInnerx[n1][1], laBaseOuterx[n1][2] - laBaseInnerx[n1][2] ] - # displace to get bottom points - lavInnerx = [ [], laBaseInnerx ] - for n1 in range(elementsCountAroundAtria): - lavInnerx[0].append([ laBaseInnerx[n1][0], laBaseInnerx[n1][1], laBaseInnerx[n1][2] - fibrousRingThickness ]) - lavInnerd1 = [ laBaseInnerd1, laBaseInnerd1 ] - lavInnerd2 = [ [ zero ]*elementsCountAroundAtria, [ zero ]*elementsCountAroundAtria ] - lavInnerd3 = [ laBaseInnerd3, laBaseInnerd3 ] - lavOuterx = [ [], laBaseOuterx ] - for n1 in range(elementsCountAroundAtria): - lavOuterx[0].append([ laBaseOuterx[n1][0], laBaseOuterx[n1][1], laBaseOuterx[n1][2] - fibrousRingThickness ] if (laBaseOuterx[n1]) else None) - lavOuterd1 = [ laBaseOuterd1, laBaseOuterd1 ] - lavOuterd2 = [ [ zero ]*elementsCountAroundAtria, [ zero ]*elementsCountAroundAtria ] - lavOuterd3 = [ laBaseOuterd3, laBaseOuterd3 ] - # Right av fibrous ring points = mirror - ravInnerx = [ [], [] ] - ravInnerd1 = [ [], [] ] - ravInnerd2 = [ [], [] ] - ravInnerd3 = [ [], [] ] - ravOuterx = [ [], [] ] - ravOuterd1 = [ [], [] ] - ravOuterd2 = [ [], [] ] - ravOuterd3 = [ [], [] ] - for n2 in range(2): - for n1 in range(elementsCountAroundAtria): - n1l = elementsCountAroundAtria - 1 - n1 - lavx = lavInnerx [n2][n1l] - lavd1 = lavInnerd1[n2][n1l] - lavd2 = lavInnerd2[n2][n1l] - lavd3 = lavInnerd3[n2][n1l] - ravInnerx [n2].append([ -lavx [0], lavx [1], lavx [2] ]) - ravInnerd1[n2].append([ lavd1[0], -lavd1[1], -lavd1[2] ]) - ravInnerd2[n2].append([ -lavd2[0], lavd2[1], lavd2[2] ]) - ravInnerd3[n2].append([ -lavd3[0], lavd3[1], lavd3[2] ]) - if lavOuterx[n2][n1l]: - lavx = lavOuterx [n2][n1l] - lavd1 = lavOuterd1[n2][n1l] - lavd2 = lavOuterd2[n2][n1l] - lavd3 = lavOuterd3[n2][n1l] - ravOuterx [n2].append([ -lavx [0], lavx [1], lavx [2] ]) - ravOuterd1[n2].append([ lavd1[0], -lavd1[1], -lavd1[2] ]) - ravOuterd2[n2].append([ -lavd2[0], lavd2[1], lavd2[2] ]) - ravOuterd3[n2].append([ -lavd3[0], lavd3[1], lavd3[2] ]) + # displace to get points on bottom av fibrous ring + lavx = [ [], [] ] + lavd1 = [ [], [] ] + lavd2 = [ [], [] ] + lavd3 = [ [], [] ] + ravx = [ [], [] ] + ravd1 = [ [], [] ] + ravd2 = [ [], [] ] + ravd3 = [ [], [] ] + for n3 in range(2): + lavx[n3] = [ [], labx[n3] ] + for n1 in range(elementsCountAroundLeftAtrium): + if labx[n3][n1]: + lavx[n3][0].append([ labx[n3][n1][0], labx[n3][n1][1], labx[n3][n1][2] - fibrousRingThickness ]) else: - # no outer points around septum - ravOuterx [n2].append(None) - ravOuterd1[n2].append(None) - ravOuterd2[n2].append(None) - ravOuterd3[n2].append(None) + lavx[n3][0].append(None) # no outer points around septum + lavd1[n3] = [ labd1[n3], labd1[n3] ] + lavd2[n3] = [ [ zero ]*elementsCountAroundLeftAtrium, [ zero ]*elementsCountAroundLeftAtrium ] + lavd3[n3] = [ labd3[n3], labd3[n3] ] + ravx[n3] = [ [], rabx[n3] ] + for n1 in range(elementsCountAroundRightAtrium): + if rabx[n3][n1]: + ravx[n3][0].append([ rabx[n3][n1][0], rabx[n3][n1][1], rabx[n3][n1][2] - fibrousRingThickness ]) + else: + ravx[n3][0].append(None) # no outer points around septum + ravd1[n3] = [ rabd1[n3], rabd1[n3] ] + ravd2[n3] = [ [ zero ]*elementsCountAroundRightAtrium, [ zero ]*elementsCountAroundRightAtrium ] + ravd3[n3] = [ rabd3[n3], rabd3[n3] ] + # get av bottom derivative 2 from Hermite-Lagrange interpolation from top row of ventricles elementsCountLVFreeWallRegular = elementsCountAroundLVFreeWall - 1 for n1 in range(elementsCountLVFreeWallRegular + 1): - noa = elementsCountAroundAtrialFreeWall - elementsCountLVFreeWallRegular + n1 + noa = elementsCountAroundLeftAtriumFreeWall - elementsCountLVFreeWallRegular + n1 nov = elementsCountAroundLVFreeWall - elementsCountLVFreeWallRegular + n1 - lavInnerd2[0][noa] = interp.interpolateHermiteLagrangeDerivative(lvInnerx[nov], lvInnerd2[nov], lavInnerx[0][noa], 1.0) - lavOuterd2[0][noa] = interp.interpolateHermiteLagrangeDerivative(vOuterx[nov], vOuterd2[nov], lavOuterx[0][noa], 1.0) + lavd2[0][0][noa] = interp.interpolateHermiteLagrangeDerivative(lvInnerx[nov], lvInnerd2[nov], lavx[0][0][noa], 1.0) + lavd2[1][0][noa] = interp.interpolateHermiteLagrangeDerivative(vOuterx[nov], vOuterd2[nov], lavx[1][0][noa], 1.0) if n1 == 0: # add d1 to d2 since subtracted in use: for c in range(3): - lavInnerd2[0][noa][c] += lavInnerd1[0][noa][c] - lavOuterd2[0][noa][c] += lavOuterd1[0][noa][c] - elementsCountRVHanging = 2 if (elementsCountAroundAtrialFreeWall == 8) else 0 + lavd2[0][0][noa][c] += lavd1[0][0][noa][c] + lavd2[1][0][noa][c] += lavd1[1][0][noa][c] + elementsCountRVHanging = 2 if (elementsCountAroundRightAtriumFreeWall == 8) else 0 elementsCountRVFreeWallRegular = 3 + elementsCountRVHanging for n1 in range(elementsCountRVFreeWallRegular + 1): - noa = elementsCountAroundAtrialSeptum + n1 - 1 + noa = n1 niv = n1 nov = elementsCountAroundLVFreeWall + n1 if elementsCountRVHanging: @@ -482,34 +519,34 @@ def generateBaseMesh(cls, region, options): else: six, sid2 = rvInnerx[niv], rvInnerd2[niv] sox, sod2 = vOuterx[nov], vOuterd2[nov] - ravInnerd2[0][noa] = interp.interpolateHermiteLagrangeDerivative(six, sid2, ravInnerx[0][noa], 1.0) - ravOuterd2[0][noa] = interp.interpolateHermiteLagrangeDerivative(sox, sod2, ravOuterx[0][noa], 1.0) + ravd2[0][0][noa] = interp.interpolateHermiteLagrangeDerivative(six, sid2, ravx[0][0][noa], 1.0) + ravd2[1][0][noa] = interp.interpolateHermiteLagrangeDerivative(sox, sod2, ravx[1][0][noa], 1.0) if elementsCountRVHanging: # subtract d1 on last point, later map to d1 + d2 - noa = elementsCountAroundAtrialSeptum + 4 + noa = 5 for c in range(3): - ravInnerd2[0][noa][c] -= ravInnerd1[0][noa][c] - ravOuterd2[0][noa][c] -= ravOuterd1[0][noa][c] + ravd2[0][0][noa][c] -= ravd1[0][0][noa][c] + ravd2[1][0][noa][c] -= ravd1[1][0][noa][c] for n1 in range(elementsCountAroundAtrialSeptum + 1): - noa = (elementsCountAroundAtrialFreeWall + n1)%elementsCountAroundAtria + noa = (elementsCountAroundLeftAtriumFreeWall + n1)%elementsCountAroundLeftAtrium nov = elementsCountAroundLVFreeWall + n1 - lavInnerd2[0][noa] = interp.interpolateHermiteLagrangeDerivative(lvInnerx[nov], lvInnerd2[nov], lavInnerx[0][noa], 1.0) - noa = (elementsCountAroundAtrialSeptum - 1 + elementsCountAroundAtria - n1)%elementsCountAroundAtria + lavd2[0][0][noa] = interp.interpolateHermiteLagrangeDerivative(lvInnerx[nov], lvInnerd2[nov], lavx[0][0][noa], 1.0) + noa = -n1 nov = -n1 - ravInnerd2[0][noa] = interp.interpolateHermiteLagrangeDerivative(rvInnerx[nov], rvInnerd2[nov], ravInnerx[0][noa], 1.0) + ravd2[0][0][noa] = interp.interpolateHermiteLagrangeDerivative(rvInnerx[nov], rvInnerd2[nov], ravx[0][0][noa], 1.0) # special fix for left cfb - lavInnerd2[0][1] = interp.interpolateHermiteLagrangeDerivative(lvOutletInnerx[-1], [ -d for d in lvOutletd2 ], lavInnerx[0][1], 1.0) + lavd2[0][0][1] = interp.interpolateHermiteLagrangeDerivative(lvOutletInnerx[-1], [ -d for d in lvOutletd2 ], lavx[0][0][1], 1.0) # special fix for left-central cfb (fix from above) sd2 = [ (-lvOutletInnerd1[0][c] - lvOutletd2[c]) for c in range(3) ] - pd2 = interp.smoothCubicHermiteDerivativesLine([ lvOutletInnerx[0], lavInnerx[0][0] ], [ sd2, lavInnerd2[0][0] ], fixStartDerivative=True, fixEndDirection=True) - lavInnerd2[0][0] = pd2[1] + pd2 = interp.smoothCubicHermiteDerivativesLine([ lvOutletInnerx[0], lavx[0][0][0] ], [ sd2, lavd2[0][0][0] ], fixStartDerivative=True, fixEndDirection=True) + lavd2[0][0][0] = pd2[1] # special fix for right cfb - noa = elementsCountAroundAtria - 2 + noa = elementsCountAroundRightAtriumFreeWall - 1 nov = -elementsCountAroundAtrialSeptum - 1 mag = baseHeight + baseThickness - d2 = vector.setMagnitude(vector.crossproduct3(ravInnerd3[0][noa], ravInnerd1[0][noa]), mag) - pd2 = interp.smoothCubicHermiteDerivativesLine([ rvInnerx[nov], ravInnerx[0][noa]], [ rvInnerd2[nov], d2 ], fixStartDerivative=True, fixEndDirection=True) - ravInnerd2[0][noa] = pd2[1] + d2 = vector.setMagnitude(vector.crossproduct3(ravd3[0][0][noa], ravd1[0][0][noa]), mag) + pd2 = interp.smoothCubicHermiteDerivativesLine([ rvInnerx[nov], ravx[0][0][noa]], [ rvInnerd2[nov], d2 ], fixStartDerivative=True, fixEndDirection=True) + ravd2[0][0][noa] = pd2[1] datapoint = datapoints.createNode(-1, datapointTemplateExternal) cache.setNode(datapoint) @@ -522,16 +559,16 @@ def generateBaseMesh(cls, region, options): dataLabel.assignString(cache, 'tricuspid valve ctr') # set d2 at ra node mid supraventricular crest to be normal to surface; smooth to get final magnitude later - ravsvcn1 = elementsCountAroundAtria - 3 + ravsvcn1 = elementsCountAroundRightAtriumFreeWall - 2 mag = baseHeight + baseThickness - ravInnerd2[0][ravsvcn1] = vector.setMagnitude(vector.crossproduct3(ravInnerd3[0][ravsvcn1], ravInnerd1[0][ravsvcn1]), mag) - ravOuterd2[0][ravsvcn1] = vector.setMagnitude(vector.crossproduct3(ravOuterd3[0][ravsvcn1], ravOuterd1[0][ravsvcn1]), mag) - ravsvcn2 = elementsCountAroundAtria - 4 + ravd2[0][0][ravsvcn1] = vector.setMagnitude(vector.crossproduct3(ravd3[0][0][ravsvcn1], ravd1[0][0][ravsvcn1]), mag) + ravd2[1][0][ravsvcn1] = vector.setMagnitude(vector.crossproduct3(ravd3[1][0][ravsvcn1], ravd1[1][0][ravsvcn1]), mag) + ravsvcn2 = elementsCountAroundRightAtriumFreeWall - 3 # copy derivative 3 from av points to LV outlet at centre, left and right cfb; negate as d1 is reversed: - lvOutletOuterd3[0] = [ -d for d in lavOuterd3[0][0] ] - lvOutletOuterd3[1] = [ -d for d in ravOuterd3[0][-2] ] - lvOutletOuterd3[-1] = [ -d for d in lavOuterd3[0][1] ] + lvOutletOuterd3[0] = [ -d for d in lavd3[1][0][0] ] + lvOutletOuterd3[1] = [ -d for d in ravd3[1][0][-2] ] + lvOutletOuterd3[-1] = [ -d for d in lavd3[1][0][1] ] # create point above anterior ventricular septum end xi = 0.5 @@ -541,12 +578,12 @@ def generateBaseMesh(cls, region, options): x = interp.interpolateCubicHermite(vOuterx[0], vOuterd2[0], lvOutletOuterx[4], fd2, xi) d2 = interp.interpolateCubicHermiteDerivative(vOuterx[0], vOuterd2[0], lvOutletOuterx[4], fd2, xi) pd2 = interp.smoothCubicHermiteDerivativesLine([ vOuterx[0], x, lvOutletOuterx[4] ], [ vOuterd2[0], d2, lvOutletd2 ], fixAllDirections=True, fixStartDerivative=True, fixEndDerivative=True) - pd1 = interp.smoothCubicHermiteDerivativesLine([ rvOutletOuterx[-1], x, lavOuterx[0][2] ], [ [ -d for d in rvOutletd2 ], zero, lavOuterd2[0][2] ], fixStartDerivative=True, fixEndDirection=True, magnitudeScalingMode=interp.DerivativeScalingMode.HARMONIC_MEAN) + pd1 = interp.smoothCubicHermiteDerivativesLine([ rvOutletOuterx[-1], x, lavx[1][0][2] ], [ [ -d for d in rvOutletd2 ], zero, lavd2[1][0][2] ], fixStartDerivative=True, fixEndDirection=True, magnitudeScalingMode=interp.DerivativeScalingMode.HARMONIC_MEAN) avsx = x avsd1 = pd1[1] avsd2 = pd2[1] avsd3 = interp.interpolateHermiteLagrangeDerivative(lvInnerx[0], lvInnerd2[0], avsx, 1.0) - lavOuterd2[0][2] = pd1[2] + lavd2[1][0][2] = pd1[2] # create points on bottom and top of RV supraventricular crest ns = elementsCountAroundRVFreeWall//2 + 1 @@ -555,12 +592,12 @@ def generateBaseMesh(cls, region, options): xif = 1.0 - xis mx = [ xis*rvInnerx[ns][0] + xif*rvInnerx[nf][0], xis*rvInnerx[ns][1] + xif*rvInnerx[nf][1], -(fibrousRingThickness + baseThickness) ] md2 = [ (rvInnerx[nf][c] - rvInnerx[ns][c]) for c in range(3) ] - sd1 = [ -d for d in ravInnerd2[0][ravsvcn2] ] + sd1 = [ -d for d in ravd2[0][0][ravsvcn2] ] if elementsCountRVHanging == 0: for c in range(3): - sd1[c] += ravInnerd1[0][ravsvcn2][c] + sd1[c] += ravd1[0][0][ravsvcn2][c] fd1 = [ (rvOutletInnerd1[2][c] + rvOutletd2[c]) for c in range(3) ] - pd1 = interp.smoothCubicHermiteDerivativesLine([ ravInnerx[0][ravsvcn2], mx, rvOutletInnerx[1] ], [ sd1, zero, fd1 ], + pd1 = interp.smoothCubicHermiteDerivativesLine([ ravx[0][0][ravsvcn2], mx, rvOutletInnerx[1] ], [ sd1, zero, fd1 ], fixStartDerivative=True, fixEndDerivative = True) pd2 = interp.smoothCubicHermiteDerivativesLine([ rvInnerx[ns], mx, rvInnerx[nf] ], [ rvInnerd2[ns], md2, [ -d for d in rvInnerd2[nf] ] ], fixStartDerivative = True, fixEndDerivative = True) @@ -569,30 +606,30 @@ def generateBaseMesh(cls, region, options): svcid2 = pd2[1] svcid3 = vector.setMagnitude(vector.crossproduct3(svcid1, svcid2), baseThickness) sd2 = [ (svcid2[c] - svcid1[c]) for c in range(3) ] - pd2 = interp.smoothCubicHermiteDerivativesLine([ mx, ravInnerx[0][ravsvcn1] ], [ sd2, ravInnerd2[0][ravsvcn1] ], + pd2 = interp.smoothCubicHermiteDerivativesLine([ mx, ravx[0][0][ravsvcn1] ], [ sd2, ravd2[0][0][ravsvcn1] ], fixStartDerivative=True, fixEndDirection = True) - ravInnerd2[0][ravsvcn1] = pd2[1] + ravd2[0][0][ravsvcn1] = pd2[1] mx = [ (mx[c] + svcid3[c]) for c in range(3) ] md2 = svcid2 nf = 2 - sd1 = [ -d for d in ravOuterd2[0][ravsvcn2] ] + sd1 = [ -d for d in ravd2[1][0][ravsvcn2] ] if elementsCountRVHanging == 0: for c in range(3): - sd1[c] += ravOuterd1[0][ravsvcn2][c] + sd1[c] += ravd1[1][0][ravsvcn2][c] fd1 = [ (rvOutletOuterd1[2][c] + rvOutletd2[c]) for c in range(3) ] - pd1 = interp.smoothCubicHermiteDerivativesLine([ ravOuterx[0][ravsvcn2], mx, rvOutletOuterx[1] ], [ sd1, zero, fd1 ], + pd1 = interp.smoothCubicHermiteDerivativesLine([ ravx[1][0][ravsvcn2], mx, rvOutletOuterx[1] ], [ sd1, zero, fd1 ], fixStartDerivative=True, fixEndDerivative = True) pd2 = interp.smoothCubicHermiteDerivativesLine([ mx, lvOutletOuterx[nf] ], [ md2, svcid2 ], fixStartDirection=True) # , instrument=True) - svcox = [ mx[0], mx[1], mx[2] ] # list components to avoid reference bug + svcox = copy.copy(mx) svcod1 = pd1[1] svcod2 = pd2[0] svcod3 = svcid3 lvOutletOuterd3[nf] = [ -d for d in pd2[1] ] sd2 = [ (svcod2[c] - svcod1[c]) for c in range(3) ] - pd2 = interp.smoothCubicHermiteDerivativesLine([ mx, ravOuterx[0][ravsvcn1] ], [ sd2, ravOuterd2[0][ravsvcn1] ], + pd2 = interp.smoothCubicHermiteDerivativesLine([ mx, ravx[1][0][ravsvcn1] ], [ sd2, ravd2[1][0][ravsvcn1] ], fixStartDerivative=True, fixEndDirection = True) - ravOuterd2[0][ravsvcn1] = pd2[1] + ravd2[1][0][ravsvcn1] = pd2[1] # LV outlet nodes lvOutletNodeId = [ [], [] ] @@ -647,28 +684,20 @@ def generateBaseMesh(cls, region, options): nodeIdentifier += 1 # AV fibrous ring nodes - lavInnerNodeId = [ [], [] ] - lavOuterNodeId = [ [], [] ] - ravInnerNodeId = [ [], [] ] - ravOuterNodeId = [ [], [] ] + lavNodeId = [ [ [], [] ], [ [], [] ] ] + ravNodeId = [ [ [], [] ], [ [], [] ] ] for n3 in range(2): for n2 in range(1): # 2): for i in range(2): - if n3 == 0: - if i == 0: - avx, avd1, avd2, avd3 = lavInnerx[n2], lavInnerd1[n2], lavInnerd2[n2], lavInnerd3[n2] - avNodeId = lavInnerNodeId[n2] - else: - avx, avd1, avd2, avd3 = ravInnerx[n2], ravInnerd1[n2], ravInnerd2[n2], ravInnerd3[n2] - avNodeId = ravInnerNodeId[n2] + if i == 0: + avx, avd1, avd2, avd3 = lavx[n3][n2], lavd1[n3][n2], lavd2[n3][n2], lavd3[n3][n2] + avNodeId = lavNodeId[n3][n2] + elementsCountAroundAtrium = elementsCountAroundLeftAtrium else: - if i == 0: - avx, avd1, avd2, avd3 = lavOuterx[n2], lavOuterd1[n2], lavOuterd2[n2], lavOuterd3[n2] - avNodeId = lavOuterNodeId[n2] - else: - avx, avd1, avd2, avd3 = ravOuterx[n2], ravOuterd1[n2], ravOuterd2[n2], ravOuterd3[n2] - avNodeId = ravOuterNodeId[n2] - for n1 in range(elementsCountAroundAtria): + avx, avd1, avd2, avd3 = ravx[n3][n2], ravd1[n3][n2], ravd2[n3][n2], ravd3[n3][n2] + avNodeId = ravNodeId[n3][n2] + elementsCountAroundAtrium = elementsCountAroundRightAtrium + for n1 in range(elementsCountAroundAtrium): if avx[n1] is None: avNodeId.append(None) continue @@ -678,12 +707,12 @@ def generateBaseMesh(cls, region, options): if (i == 0) and (n1 <= 1): avNodeId.append(lvOutletNodeId[1][0] if (n1 == 0) else lvOutletNodeId[1][-1]) continue - elif (i == 1) and (n1 >= (elementsCountAroundAtria - 2)): - avNodeId.append(lvOutletNodeId[1][0] if (n1 == (elementsCountAroundAtria - 1)) else lvOutletNodeId[1][1]) + elif (i == 1) and (n1 in [ elementsCountAroundRightAtriumFreeWall - 1, elementsCountAroundRightAtriumFreeWall ]): + avNodeId.append(lvOutletNodeId[1][0] if (n1 == elementsCountAroundRightAtriumFreeWall) else lvOutletNodeId[1][1]) continue - if (i == 1) and ((n1 == (elementsCountAroundAtrialSeptum - 1)) or (n1 == (elementsCountAroundAtria - 1))): + if (i == 1) and ((n1 == 0) or (n1 == elementsCountAroundRightAtriumFreeWall)): # find common nodes on right at cfb and crux - avNodeId.append(lavOuterNodeId[n2][elementsCountAroundAtria - 1 - n1]) + avNodeId.append(lavNodeId[1][n2][elementsCountAroundLeftAtriumFreeWall if (n1 == 0) else 0]) continue avNodeId.append(nodeIdentifier) node = nodes.createNode(nodeIdentifier, nodetemplate) @@ -699,11 +728,13 @@ def generateBaseMesh(cls, region, options): fibrousRingGroup = lFibrousRingGroup if (i == 0) else rFibrousRingGroup fibrousRingNodesetGroup = fibrousRingGroup.getNodesetGroup(nodes) for n3 in range(2): - if n3 == 0: - avNodeId = lavInnerNodeId[0] if (i == 0) else ravInnerNodeId[0] + if i == 0: + avNodeId = lavNodeId[n3][0] + elementsCountAroundAtrium = elementsCountAroundLeftAtrium else: - avNodeId = lavOuterNodeId[0] if (i == 0) else ravOuterNodeId[0] - for n1 in range(elementsCountAroundAtria): + avNodeId = ravNodeId[n3][0] + elementsCountAroundAtrium = elementsCountAroundRightAtrium + for n1 in range(elementsCountAroundAtrium): if avNodeId[n1]: node = nodes.findNodeByIdentifier(avNodeId[n1]) fibrousRingNodesetGroup.addNode(node) @@ -772,10 +803,10 @@ def generateBaseMesh(cls, region, options): remapEftLocalNodes(eft1, 4, ln_map) meshGroups += [ rvMeshGroup ] else: # regular elements between LV free wall and left atrium - noa = elementsCountAroundAtrialFreeWall - elementsCountAroundLVFreeWall + e + noa = elementsCountAroundLeftAtriumFreeWall - elementsCountAroundLVFreeWall + e nov = e - nids = [ lvInnerNodeId[nov], lvInnerNodeId[nov + 1], lavInnerNodeId[0][noa], lavInnerNodeId[0][noa + 1], - vOuterNodeId[nov], vOuterNodeId[nov + 1], lavOuterNodeId[0][noa], lavOuterNodeId[0][noa + 1] ] + nids = [ lvInnerNodeId[nov], lvInnerNodeId[nov + 1], lavNodeId[0][0][noa], lavNodeId[0][0][noa + 1], + vOuterNodeId[nov], vOuterNodeId[nov + 1], lavNodeId[1][0][noa], lavNodeId[1][0][noa + 1] ] if e == 0: # 7 node collapsed hex-wedge "hedge" element nids.pop(2) @@ -821,7 +852,7 @@ def generateBaseMesh(cls, region, options): scalefactors = None meshGroups = [ rvMeshGroup ] - noa = elementsCountAroundAtrialSeptum - 1 + e + noa = e niv = e if elementsCountRVHanging: if e > 1: @@ -833,8 +864,8 @@ def generateBaseMesh(cls, region, options): novp = (nov + 1)%elementsCountAroundLV if e == -1: # crux / posterior interventricular sulcus, collapsed to 6 node wedge - nids = [ lvInnerNodeId[elementsCountAroundLVFreeWall], rvInnerNodeId[nivp], lavInnerNodeId[0][elementsCountAroundAtrialFreeWall], ravInnerNodeId[0][noa + 1], - vOuterNodeId[novp], ravOuterNodeId[0][noa + 1] ] + nids = [ lvInnerNodeId[elementsCountAroundLVFreeWall], rvInnerNodeId[nivp], lavNodeId[0][0][elementsCountAroundLeftAtriumFreeWall], ravNodeId[0][0][noa + 1], + vOuterNodeId[novp], ravNodeId[1][0][noa + 1] ] eft1 = tricubichermite.createEftNoCrossDerivatives() setEftScaleFactorIds(eft1, [1], []) scalefactors = [ -1.0 ] @@ -848,8 +879,8 @@ def generateBaseMesh(cls, region, options): remapEftLocalNodes(eft1, 6, ln_map) meshGroups += [ lvMeshGroup ] elif e < elementsCountRVFreeWallRegular: - nids = [ rvInnerNodeId[niv], rvInnerNodeId[nivp], ravInnerNodeId[0][noa], ravInnerNodeId[0][noa + 1], - vOuterNodeId[nov], vOuterNodeId[novp], ravOuterNodeId[0][noa], ravOuterNodeId[0][noa + 1] ] + nids = [ rvInnerNodeId[niv], rvInnerNodeId[nivp], ravNodeId[0][0][noa], ravNodeId[0][0][noa + 1], + vOuterNodeId[nov], vOuterNodeId[novp], ravNodeId[1][0][noa], ravNodeId[1][0][noa + 1] ] if e == 0: # general linear map d3 adjacent to collapsed crux, transition to atria eft1 = tricubichermite.createEftNoCrossDerivatives() @@ -872,8 +903,8 @@ def generateBaseMesh(cls, region, options): remapEftNodeValueLabel(eft1, [ 4, 8 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, []) ]) elif e == elementsCountRVFreeWallRegular: # supraventricular crest outer 1 - nids = [ rvInnerNodeId[niv], rvInnerNodeId[nivp], ravInnerNodeId[0][noa], svciNodeId, - vOuterNodeId[nov], vOuterNodeId[novp], ravOuterNodeId[0][noa], svcoNodeId ] + nids = [ rvInnerNodeId[niv], rvInnerNodeId[nivp], ravNodeId[0][0][noa], svciNodeId, + vOuterNodeId[nov], vOuterNodeId[novp], ravNodeId[1][0][noa], svcoNodeId ] eft1 = tricubichermite.createEftNoCrossDerivatives() setEftScaleFactorIds(eft1, [1], []) scalefactors = [ -1.0 ] @@ -984,12 +1015,12 @@ def generateBaseMesh(cls, region, options): setEftScaleFactorIds(eft1, [1], []) scalefactors = [ -1.0 ] if e < elementsCountAroundAtrialSeptum: - la1 = (elementsCountAroundAtrialFreeWall + e)%elementsCountAroundAtria - la2 = (la1 + 1)%elementsCountAroundAtria - ra1 = elementsCountAroundAtrialSeptum - 1 - e + la1 = (elementsCountAroundLeftAtriumFreeWall + e)%elementsCountAroundLeftAtrium + la2 = (la1 + 1)%elementsCountAroundLeftAtrium + ra1 = -e ra2 = ra1 - 1 - nids = [ lvInnerNodeId[lv1], lvInnerNodeId[lv2], lavInnerNodeId[0][la1], lavInnerNodeId[0][la2], - rvInnerNodeId[rv1], rvInnerNodeId[rv2], ravInnerNodeId[0][ra1], ravInnerNodeId[0][ra2] ] + nids = [ lvInnerNodeId[lv1], lvInnerNodeId[lv2], lavNodeId[0][0][la1], lavNodeId[0][0][la2], + rvInnerNodeId[rv1], rvInnerNodeId[rv2], ravNodeId[0][0][ra1], ravNodeId[0][0][ra2] ] if e == 0: # general linear map d3 adjacent to collapsed posterior interventricular sulcus scaleEftNodeValueLabels(eft1, [ 5, 6, 7, 8 ], [ Node.VALUE_LABEL_D_DS1 ], [ 1 ]) @@ -1006,9 +1037,9 @@ def generateBaseMesh(cls, region, options): scaleEftNodeValueLabels(eft1, [ 5, 6, 7, 8 ], [ Node.VALUE_LABEL_D_DS1, Node.VALUE_LABEL_D_DS3 ], [ 1 ]) elif e == elementsCountAroundAtrialSeptum: # cfb: 5 node inclined pyramid element - la1 = (elementsCountAroundAtrialFreeWall + e)%elementsCountAroundAtria - ra1 = elementsCountAroundAtrialSeptum - 1 - e - nids = [ lvInnerNodeId[lv1], lvOutletNodeId[1][0], lavInnerNodeId[0][la1], rvInnerNodeId[rv1], ravInnerNodeId[0][ra1] ] + la1 = (elementsCountAroundLeftAtriumFreeWall + e)%elementsCountAroundLeftAtrium + ra1 = -e + nids = [ lvInnerNodeId[lv1], lvOutletNodeId[1][0], lavNodeId[0][0][la1], rvInnerNodeId[rv1], ravNodeId[0][0][ra1] ] remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) remapEftNodeValueLabel(eft1, [ 2, 4, 6, 8 ], Node.VALUE_LABEL_D_DS2, []) remapEftNodeValueLabel(eft1, [ 2 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, []) ]) @@ -1098,8 +1129,8 @@ def generateBaseMesh(cls, region, options): remapEftLocalNodes(eft1, 7, ln_map) elif e == 1: # 6 node collapsed wedge element mid lv 'crest' - nids = [ lvInnerNodeId[0], lavInnerNodeId[0][2], lvOutletNodeId[0][4], - avsNodeId, lavOuterNodeId[0][2], lvOutletNodeId[1][4] ] + nids = [ lvInnerNodeId[0], lavNodeId[0][0][2], lvOutletNodeId[0][4], + avsNodeId, lavNodeId[1][0][2], lvOutletNodeId[1][4] ] remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS2, [] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) remapEftNodeValueLabel(eft1, [ 2, 6 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS2, [1] ) ]) @@ -1112,8 +1143,8 @@ def generateBaseMesh(cls, region, options): remapEftLocalNodes(eft1, 6, ln_map) elif e == 2: # 7 node collapsed element where lv outlet ring expands into - nids = [ lavInnerNodeId[0][2], lavInnerNodeId[0][1], lvOutletNodeId[0][4], lvOutletNodeId[0][5], - lavOuterNodeId[0][2], lavOuterNodeId[0][1], lvOutletNodeId[1][4] ] + nids = [ lavNodeId[0][0][2], lavNodeId[0][0][1], lvOutletNodeId[0][4], lvOutletNodeId[0][5], + lavNodeId[1][0][2], lavNodeId[1][0][1], lvOutletNodeId[1][4] ] remapEftNodeValueLabel(eft1, [ 1, 2, 5 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [1] ) ]) remapEftNodeValueLabel(eft1, [ 1, 5 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS2, [1] ) ]) remapEftNodeValueLabel(eft1, [ 2 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS2, [1] ) ]) @@ -1128,7 +1159,7 @@ def generateBaseMesh(cls, region, options): elif e == 3: # 6 node wedge element bridge/curtain between mitral and aortic valve orifices no = e - 4 - nids = [ lavInnerNodeId[0][1], lavInnerNodeId[0][0], lvOutletNodeId[0][no], lvOutletNodeId[0][no + 1], lvOutletNodeId[1][no], lvOutletNodeId[1][no + 1] ] + nids = [ lavNodeId[0][0][1], lavNodeId[0][0][0], lvOutletNodeId[0][no], lvOutletNodeId[0][no + 1], lvOutletNodeId[1][no], lvOutletNodeId[1][no + 1] ] remapEftNodeValueLabel(eft1, [ 1, 2 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [1] ) ]) remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS2, [1] ) ]) remapEftNodeValueLabel(eft1, [ 2 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, [1] ) ]) @@ -1144,7 +1175,7 @@ def generateBaseMesh(cls, region, options): elif e == 4: # tetrahedral cfb shim-bridge connector element ni = elementsCountAroundLVFreeWall + elementsCountAroundAtrialSeptum - nids = [ lavInnerNodeId[0][0], lvInnerNodeId[ni], lvOutletNodeId[0][0], lvOutletNodeId[1][0] ] + nids = [ lavNodeId[0][0][0], lvInnerNodeId[ni], lvOutletNodeId[0][0], lvOutletNodeId[1][0] ] remapEftNodeValueLabel(eft1, [ 1, 2 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [1] ) ]) remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, [1] ) ]) remapEftNodeValueLabel(eft1, [ 2 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) @@ -1200,7 +1231,8 @@ def generateBaseMesh(cls, region, options): if e == 0: # 6 node collapsed vs-ra shim element rvin1 = -elementsCountAroundAtrialSeptum - nids = [ lvOutletNodeId[1][0], lvOutletNodeId[1][1], rvInnerNodeId[rvin1], rvInnerNodeId[rvin1 - 1], ravInnerNodeId[0][-1], ravInnerNodeId[0][-2] ] + nids = [ lvOutletNodeId[1][0], lvOutletNodeId[1][1], + rvInnerNodeId[rvin1], rvInnerNodeId[rvin1 - 1], ravNodeId[0][0][elementsCountAroundRightAtriumFreeWall], ravNodeId[0][0][elementsCountAroundRightAtriumFreeWall - 1] ] eft1 = tricubichermite.createEftNoCrossDerivatives() scalefactors = [ -1.0 ] setEftScaleFactorIds(eft1, [1], []) @@ -1216,7 +1248,8 @@ def generateBaseMesh(cls, region, options): elif e == 1: # 7-node collapsed rv crest inner 1, by RA-LV outlet junction rvin1 = -elementsCountAroundAtrialSeptum - 1 - nids = [ ravInnerNodeId[0][-3], rvInnerNodeId[rvin1 - 1], ravInnerNodeId[0][-2], rvInnerNodeId[rvin1], ravOuterNodeId[0][-3], lvOutletNodeId[1][2], lvOutletNodeId[1][1] ] + nids = [ ravNodeId[0][0][elementsCountAroundRightAtriumFreeWall - 2], rvInnerNodeId[rvin1 - 1], ravNodeId[0][0][elementsCountAroundRightAtriumFreeWall - 1], rvInnerNodeId[rvin1], \ + ravNodeId[1][0][elementsCountAroundRightAtriumFreeWall - 2], lvOutletNodeId[1][2], lvOutletNodeId[1][1] ] eft1 = tricubichermite.createEftNoCrossDerivatives() setEftScaleFactorIds(eft1, [1], []) scalefactors = [ -1.0 ] @@ -1240,7 +1273,8 @@ def generateBaseMesh(cls, region, options): elif e == 2: # 8-node rv crest row 2 element 1 rvin1 = -elementsCountAroundAtrialSeptum - 2 - nids = [ ravInnerNodeId[0][-4], svciNodeId, ravInnerNodeId[0][-3], rvInnerNodeId[rvin1], ravOuterNodeId[0][-4], svcoNodeId, ravOuterNodeId[0][-3], lvOutletNodeId[1][2] ] + nids = [ ravNodeId[0][0][elementsCountAroundRightAtriumFreeWall - 3], svciNodeId, ravNodeId[0][0][elementsCountAroundRightAtriumFreeWall - 2], rvInnerNodeId[rvin1], \ + ravNodeId[1][0][elementsCountAroundRightAtriumFreeWall - 3], svcoNodeId, ravNodeId[1][0][elementsCountAroundRightAtriumFreeWall - 2], lvOutletNodeId[1][2] ] eft1 = tricubichermite.createEftNoCrossDerivatives() setEftScaleFactorIds(eft1, [1], []) scalefactors = [ -1.0 ] @@ -1340,9 +1374,9 @@ def refineMesh(cls, meshrefinement, options): elementsCountUpLVApex = options['Number of elements up LV apex'] elementsCountUpRV = options['Number of elements up RV'] elementsCountUpLV = elementsCountUpLVApex + elementsCountUpRV - elementsCountAroundAtrialFreeWall = options['Number of elements around atrial free wall'] elementsCountAroundAtrialSeptum = options['Number of elements around atrial septum'] - elementsCountAroundAtria = elementsCountAroundAtrialFreeWall + elementsCountAroundAtrialSeptum + elementsCountAroundLeftAtriumFreeWall = options['Number of elements around left atrium free wall'] + elementsCountAroundRightAtriumFreeWall = options['Number of elements around right atrium free wall'] refineElementsCountSurface = options['Refine number of elements surface'] refineElementsCountThroughLVWall = options['Refine number of elements through LV wall'] refineElementsCountThroughWall = options['Refine number of elements through wall'] @@ -1350,7 +1384,7 @@ def refineMesh(cls, meshrefinement, options): element = meshrefinement._sourceElementiterator.next() startBaseLvElementIdentifier = element.getIdentifier() startBaseRvElementIdentifier = startBaseLvElementIdentifier + elementsCountAroundLVFreeWall + 1 - elementsCountRVHanging = 2 if (elementsCountAroundAtrialFreeWall == 8) else 0 + elementsCountRVHanging = 2 if (elementsCountAroundRightAtriumFreeWall == 8) else 0 startBaseSeptumElementIdentifier = startBaseRvElementIdentifier + elementsCountAroundRVFreeWall + elementsCountRVHanging + 3 startBaseLv2ElementIdentifier = startBaseSeptumElementIdentifier + elementsCountAroundVSeptum + 1 startBaseRv2ElementIdentifier = startBaseLv2ElementIdentifier + 8 diff --git a/scaffoldmaker/meshtypes/meshtype_3d_ostium1.py b/scaffoldmaker/meshtypes/meshtype_3d_ostium1.py index c22ba9f6..0d3bc848 100644 --- a/scaffoldmaker/meshtypes/meshtype_3d_ostium1.py +++ b/scaffoldmaker/meshtypes/meshtype_3d_ostium1.py @@ -6,6 +6,7 @@ import copy import math from scaffoldmaker.meshtypes.scaffold_base import Scaffold_base +from scaffoldmaker.utils.annulusmesh import createAnnulusMesh3d from scaffoldmaker.utils.eftfactory_tricubichermite import eftfactory_tricubichermite from scaffoldmaker.utils.geometry import createCirclePoints, getCircleProjectionAxes from scaffoldmaker.utils import interpolation as interp @@ -29,7 +30,7 @@ def getDefaultOptions(parameterSetName='Default'): return { 'Number of vessels' : 2, 'Number of elements across common' : 2, - 'Number of elements around vessel' : 8, + 'Number of elements around ostium' : 10, 'Number of elements along' : 1, 'Number of elements through wall' : 1, # not implemented for > 1 'Unit scale' : 1.0, @@ -44,8 +45,8 @@ def getDefaultOptions(parameterSetName='Default'): 'Vessel inner diameter' : 0.6, 'Vessel wall thickness' : 0.04, 'Vessel angle 1 degrees' : 0.0, - 'Vessel angle 1 spread degrees' : 30.0, - 'Vessel angle 2 degrees' : -20.0, + 'Vessel angle 1 spread degrees' : 0.0, + 'Vessel angle 2 degrees' : 0.0, 'Use linear through vessel wall' : True, #'Use cross derivatives' : False, 'Refine' : False, @@ -59,7 +60,7 @@ def getOrderedOptionNames(): return [ 'Number of vessels', 'Number of elements across common', - 'Number of elements around vessel', + 'Number of elements around ostium', 'Number of elements along', 'Number of elements through wall', # not implemented for > 1 'Unit scale', @@ -87,11 +88,11 @@ def getOrderedOptionNames(): @staticmethod def checkOptions(options): dependentChanges = False - for key in ['Number of vessels']: - if options[key] < 1: - options[key] = 1 - elif options[key] > 2: - options[key] = 2 + vesselsCount = options['Number of vessels'] + if vesselsCount < 1: + vesselsCount = options['Number of vessels'] = 1 + elif vesselsCount > 3: + vesselsCount = options['Number of vessels'] = 3 for key in [ 'Number of elements across common', 'Number of elements along', @@ -101,13 +102,15 @@ def checkOptions(options): 'Refine number of elements through wall']: if options[key] < 1: options[key] = 1 + if (options['Number of elements around ostium'] < 2*vesselsCount) : + options['Number of elements around ostium'] = 2*vesselsCount + dependentChanges = True # because can happen by changing number of vessels + # currently must have even number around ostium if multiple vessels: + if (vesselsCount > 1) and (options['Number of elements around ostium'] % 2): + options['Number of elements around ostium'] += 1 + dependentChanges = True # because can happen by changing number of vessels if options['Number of elements through wall'] > 1: options['Number of elements through wall'] = 1 - if (options['Number of elements around vessel'] < 3) : - options['Number of elements around vessel'] = 3 - if (options['Number of elements around vessel'] - options['Number of elements across common']) < 2: - options['Number of elements around vessel'] = options['Number of elements across common'] + 2 - dependentChanges = True for key in [ 'Unit scale', 'Ostium length', @@ -140,8 +143,8 @@ def generateBaseMesh(region, options): trackSurface = TrackSurface(1, 1, nx, nd1, nd2) centrePosition = TrackSurfacePosition(0, 0, 0.5, 0.5) axis1 = [ 1.0, 0.0, 0.0 ] - annotationGroups = generateOstiumMesh(region, options, trackSurface, centrePosition, axis1)[0] - return annotationGroups + generateOstiumMesh(region, options, trackSurface, centrePosition, axis1) + return [] # no annotation groups @classmethod def generateMesh(cls, region, options): @@ -162,17 +165,54 @@ def generateMesh(cls, region, options): return meshrefinement.getAnnotationGroups() +def getOstiumElementsCountsAroundVessels(elementsCountAroundOstium, elementsCountAcross, vesselsCount): + ''' + Determine numbers of elements around each vessel to fit numbers of elements around ostium. + :param elementsCountAroundOstium: Number of elements around outside of ostium. + Minimum of 2 + 2*vesselsCount. Must be even if more than 1 vessels. + :param elementsCountAcross: Number of elements across ostium between multiple vessels. + Unused if vesselsCount is 1. + :param vesselsCount: Number of vessels from 1 to 3. + :return: List of numbers of elements around each vessel to fit numbers around ostium, + number of elements mid side (non-zero only for 3+ vessels). + ''' + assert 1 <= vesselsCount <= 3 + assert elementsCountAroundOstium >= 2*vesselsCount + assert (1 == vesselsCount) or (elementsCountAroundOstium%2 == 0) + assert elementsCountAcross > 0 + if vesselsCount == 1: + return [ elementsCountAroundOstium ], 0 + if vesselsCount == 2: + count = elementsCountAroundOstium//2 + elementsCountAcross + return [ count, count ], 0 + # vesselsCount == 3: + # Around oinc Total:Vessels 1 Total:Vessels 2 + # 6 1 8: 3-4-3 10: 4-6-4 + # 8 1 10: 4-4-4 12: 5-6-5 + # 10 1 12: 5-4-5 14: 6-6-6 + # 12 2 14: 5-6-5 16: 6-8-6 + # 14 2 16: 6-6-6 18: 7-8-7 + # 16 2 18: 7-6-7 20: 8-8-8 + elementsCountAroundMid = ((elementsCountAroundOstium + 1)//6) + countInner = 2*(elementsCountAroundMid + elementsCountAcross) + countOuter = (elementsCountAroundOstium - 2*elementsCountAroundMid)//2 + elementsCountAcross + return [ countOuter, countInner, countOuter ], elementsCountAroundMid + + def generateOstiumMesh(region, options, trackSurface, centrePosition, axis1, startNodeIdentifier = 1, startElementIdentifier = 1, - vesselMeshGroups = None, spacingMeshGroups = None): + vesselMeshGroups = None): ''' :param vesselMeshGroups: List (over number of vessels) of list of mesh groups to add vessel elements to. - :param spacingMeshGroups: List of mesh groups to add spacing elements to, if any. - :return: annotationGroups, nextNodeIdentifier, nextElementIdentifier, Ostium points tuple - (ox[n3][n1][c], od1[n3][n1][c], od2[n3][n1][c], od3[n3][n1][c], oNodeId[n3][n1]). + :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'] - elementsCountAroundVessel = options['Number of elements around vessel'] + 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'] @@ -195,8 +235,6 @@ def generateOstiumMesh(region, options, trackSurface, centrePosition, axis1, sta useCubicHermiteThroughVesselWall = not(options['Use linear through vessel wall']) useCrossDerivatives = False # options['Use cross derivatives'] # not implemented - annotationGroups = [] - fm = region.getFieldmodule() fm.beginChange() coordinates = zinc_utils.getOrCreateCoordinateField(fm) @@ -207,62 +245,80 @@ def generateOstiumMesh(region, options, trackSurface, centrePosition, axis1, sta # get directions in plane of surface at centre: cx, cd1, cd2 = trackSurface.evaluateCoordinates(centrePosition, True) trackDirection1, trackDirection2, centreNormal = calculate_surface_axes(cd1, cd2, axis1) - trackDirection1reverse = [ -d for d in trackDirection1 ] 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: - elementsCountAroundOstium = elementsCountAroundVessel - distanceAround = circumference - elementLengthAroundOstium = distanceAround/elementsCountAroundOstium - if elementsCountAroundVessel % 2: - distance += 0.5*elementLengthAroundOstium - ostiumCentre1 = ostiumCentre2 = centrePosition - ocx = [ cx ] + elementLengthAroundOstiumEnd = circumference/elementsCountAroundOstium + vesselOstiumPositions = [ centrePosition ] + ocx = [ cx ] + ocd1 = [ trackDirection1 ] + ocd2 = [ trackDirection2 ] + ocd3 = [ centreNormal ] else: - elementsCountAroundOstium = 2*(elementsCountAroundVessel - elementsCountAcross) - distanceAround = circumference + 2.0*interVesselDistance - elementLengthAroundOstium = distanceAround/elementsCountAroundOstium - ostiumCentre1 = trackSurface.trackVector(centrePosition, trackDirection1reverse, halfInterVesselDistance) - ostiumCentre2 = trackSurface.trackVector(centrePosition, trackDirection1, halfInterVesselDistance) - ocx = [ trackSurface.evaluateCoordinates(ostiumCentre1), trackSurface.evaluateCoordinates(ostiumCentre2) ] + 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): - if distance <= halfInterVesselDistance: - position = trackSurface.trackVector(centrePosition, trackDirection1reverse, distance) + elementLength = elementLengthAroundOstiumEnd + if distance <= (vesselsSpanMid + halfInterVesselDistance): + position = trackSurface.trackVector(centrePosition, trackDirection1, 0.5*vesselsSpanMid - distance) sideDirection = trackDirection2reverse - elif distance < (halfInterVesselDistance + halfCircumference): - position = ostiumCentre1 - angleRadians = (distance - halfInterVesselDistance)/ostiumRadius + 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 < (halfInterVesselDistance + halfCircumference + interVesselDistance): - position = trackSurface.trackVector(centrePosition, trackDirection1, distance - (interVesselDistance + halfCircumference)) + elif distance < (2.0*vesselsSpanMid + halfInterVesselDistance + halfCircumference + interVesselDistance): + position = trackSurface.trackVector(centrePosition, trackDirection1, distance - (1.5*vesselsSpanMid + interVesselDistance + halfCircumference)) sideDirection = trackDirection2 - elif distance < (halfInterVesselDistance + circumference + interVesselDistance): - position = ostiumCentre2 - angleRadians = (distance - (halfInterVesselDistance + halfCircumference + interVesselDistance))/ostiumRadius + 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, trackDirection1reverse, distance - (circumference + 2.0*interVesselDistance)) + 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 ], elementLengthAroundOstium) - opd2 = vector.setMagnitude(pd2, elementLengthAroundOstium) # smoothed later + 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) ]) @@ -274,42 +330,53 @@ def generateOstiumMesh(region, options, trackSurface, centrePosition, axis1, sta if useCubicHermiteThroughOstiumWall: od3[0].append(copy.copy(opd3)) od3[1].append(opd3) - distance += elementLengthAroundOstium + distance += elementLength for n3 in range(2): od1[n3] = interp.smoothCubicHermiteDerivativesLoop(ox[n3], od1[n3], fixAllDirections = True) - if vesselsCount == 2: - # coordinates across common ostium, between vessels - xx = [ [], [] ] - xd1 = [ [], [] ] - xd2 = [ [], [] ] - xd3 = [ [], [] ] - # sample points across crest between vessels in common ostium - oa = 0 - ob = elementsCountAroundVessel - elementsCountAcross + xx = [] + xd1 = [] + xd2 = [] + xd3 = [] + # coordinates across common ostium, between vessels + nodesCountFreeEnd = elementsCountsAroundVessels[0] + 1 - elementsCountAcross + oinc = 0 if (vesselsCount <= 2) else elementsCountAroundMid//(vesselsCount - 2) + for iv in range(vesselsCount - 1): + xx .append([ None, None ]) + xd1.append([ None, None ]) + xd2.append([ None, None ]) + xd3.append([ None, None ]) + oa = elementsCountAroundMid - iv*oinc + ob = elementsCountAroundMid + nodesCountFreeEnd - 1 + iv*oinc nx = [ ox[1][oa], ox[1][ob] ] nd1 = [ [ -d for d in od1[1][oa] ], od1[1][ob] ] nd2 = [ [ -d for d in od2[1][oa] ], od2[1][ob] ] if elementsCountAcross > 1: # add centre point, displaced by interVesselHeight - nx .insert(1, [ (cx[c] + interVesselHeight*centreNormal[c]) for c in range(3) ]) - nd1.insert(1, [ elementLengthAroundOstium*d for d in trackDirection1 ]) - nd2.insert(1, [ ostiumRadius*d for d in trackDirection2 ]) + if vesselsCount == 2: + position = centrePosition + else: + position = trackSurface.trackVector(centrePosition, trackDirection1, (iv/(vesselsCount - 2) - 0.5)*vesselsSpanMid) + mx, d1, d2 = trackSurface.evaluateCoordinates(position, derivatives = True) + md1, md2, md3 = calculate_surface_axes(d1, d2, trackDirection1) + nx .insert(1, [ (mx[c] + interVesselHeight*md3[c]) for c in range(3) ]) + nd1.insert(1, vector.setMagnitude(md1, elementLengthAroundOstiumMid if (0 < iv < (vesselsCount - 2)) else elementLengthAroundOstiumEnd)) + nd2.insert(1, vector.setMagnitude(md2, ostiumRadius)) nd2 = interp.smoothCubicHermiteDerivativesLine(nx, nd2, fixAllDirections = True) px, pd2, pe, pxi = interp.sampleCubicHermiteCurves(nx, nd2, elementsCountAcross)[0:4] pd1 = interp.interpolateSampleLinear(nd1, pe, pxi) pd3 = [ vector.setMagnitude(vector.crossproduct3(pd1[n2], pd2[n2]), ostiumWallThickness) for n2 in range(elementsCountAcross + 1) ] lx = [ ([ (px[n2][c] - pd3[n2][c]) for c in range(3) ]) for n2 in range(elementsCountAcross + 1) ] ld2 = interp.smoothCubicHermiteDerivativesLine(lx, pd2, fixAllDirections = True) - xx [0] = lx [1:elementsCountAcross] - xd1[0] = copy.deepcopy(pd1[1:elementsCountAcross]) # to be smoothed later - xd2[0] = ld2[1:elementsCountAcross] - xx [1] = px [1:elementsCountAcross] - xd1[1] = pd1[1:elementsCountAcross] # to be smoothed later - xd2[1] = pd2[1:elementsCountAcross] + 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[0] = copy.deepcopy(pd3[1:elementsCountAcross]) - xd3[1] = pd3[1:elementsCountAcross] + 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] ] @@ -325,12 +392,13 @@ def generateOstiumMesh(region, options, trackSurface, centrePosition, axis1, sta vod1 = [] vod2 = [] vod3 = [] - radiansPerElementVessel = 2.0*math.pi/elementsCountAroundVessel for v in range(vesselsCount): + elementsCountAroundVessel = elementsCountsAroundVessels[v] + radiansPerElementVessel = 2.0*math.pi/elementsCountAroundVessel useVesselAngleRadians = vesselAngle1Radians if vesselsCount > 1: - useVesselAngleRadians += (v - 0.5)*vesselAngle1SpreadRadians - vx, vd1, vd2, vd3 = getCircleProjectionAxes(ocx[v], trackDirection1, trackDirection2, centreNormal, useVesselAngleRadians, vesselAngle2Radians, ostiumLength) + 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 ] @@ -348,11 +416,9 @@ def generateOstiumMesh(region, options, trackSurface, centrePosition, axis1, sta vAxis2 = vector.setMagnitude(vd2, radius) if vesselsCount == 1: startRadians = 0.5*math.pi - if elementsCountAroundVessel % 2: - startRadians += 0.5*radiansPerElementVessel else: startRadians = 0.5*radiansPerElementVessel*elementsCountAcross - if v == 1: + if v == (vesselsCount - 1): startRadians -= math.pi px, pd1 = createCirclePoints(vx, vAxis1, vAxis2, elementsCountAroundVessel, startRadians) vox [-1].append(px) @@ -362,56 +428,120 @@ def generateOstiumMesh(region, options, trackSurface, centrePosition, axis1, sta vod3[-1].append([ vector.setMagnitude(vector.crossproduct3(d1, vd3), vesselWallThickness) for d1 in pd1 ]) # calculate common ostium vessel node derivatives map - nodesCountFree = elementsCountAroundVessel + 1 - elementsCountAcross 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: - os = 0 if (v == 0) else (nodesCountFree - 1) - of = os + nodesCountFree + 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): - mvPointsx [v].append((ox [n3]*2)[os:of]) - mvPointsd1[v].append((od1[n3]*2)[os:of]) - mvPointsd2[v].append((od2[n3]*2)[os:of]) + mvPointsd1[v].append([]) + mvPointsd2[v].append([]) + mvPointsx [v].append([]) if useCubicHermiteThroughOstiumWall: - mvPointsd3[v].append((od3[n3]*2)[os:of]) + mvPointsd3[v].append([]) mvDerivativesMap[v].append([]) - mvDerivativesMap[v][n3].append( ( (0, 1, 0), (-1, 1, 0), None, (1, 0, 0) ) ) - for i in range(nodesCountFree - 2): - mvDerivativesMap[v][n3].append( ( None, None, None ) ) - mvDerivativesMap[v][n3].append( ( (1, 0, 0), (1, 1, 0), None, (0, -1, 0) ) ) - if v == 0: - mvPointsx [v][n3] += reversed(xx [n3]) - mvPointsd1[v][n3] += reversed(xd1[n3]) - mvPointsd2[v][n3] += reversed(xd2[n3]) + 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] += reversed(xd3[n3]) + 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 ) ) - else: - mvPointsx [v][n3] += xx [n3] - mvPointsd1[v][n3] += xd1[n3] - mvPointsd2[v][n3] += xd2[n3] + 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[n3] + mvPointsd3[v][n3] += xd3[iv][n3] for i in range(elementsCountAcross - 1): mvDerivativesMap[v][n3].append( ( (0, 1, 0), (-1, 0, 0), None ) ) + if n3 == 0: + mvMeanCount[v] = [ 2 ] + [ 0 ]*(nodesCountFreeEnd - 2) + [ 2 ]*elementsCountAcross # calculate derivative 2 around free sides of inlets to fit vessel derivatives for v in range(vesselsCount): for n3 in range(2): - for n1 in range(elementsCountAroundVessel): + #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 @@ -424,11 +554,11 @@ def generateOstiumMesh(region, options, trackSurface, centrePosition, axis1, sta for c in range(3): mvPointsd2[v][n3][n1][c] = sf2*ndf[c] elif sf2 == 0: - if v == 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 first vessel + # 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: @@ -442,12 +572,12 @@ def generateOstiumMesh(region, options, trackSurface, centrePosition, axis1, sta for n1 in range(elementsCountAroundOstium): od1[n3][n1][c] = -od1[n3][n1][c] od2[n3][n1][c] = -od2[n3][n1][c] - if vesselsCount > 1: + for iv in range(vesselsCount - 1): for n1 in range(elementsCountAcross - 1): - xd1[n3][n1][c] = -xd1[n3][n1][c] - xd2[n3][n1][c] = -xd2[n3][n1][c] + 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(elementsCountAroundVessel): + 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): @@ -487,19 +617,20 @@ def generateOstiumMesh(region, options, trackSurface, centrePosition, axis1, sta oNodeId[n3].append(nodeIdentifier) nodeIdentifier += 1 - if vesselsCount > 1: - xNodeId = [] + xNodeId = [] + for iv in range(vesselsCount - 1): + xNodeId.append([]) for n3 in range(2): - xNodeId.append([]) + 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 [n3][n2]) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, xd1[n3][n2]) - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, xd2[n3][n2]) + 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[n3][n2]) - xNodeId[n3].append(nodeIdentifier) + 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): @@ -511,7 +642,7 @@ def generateOstiumMesh(region, options, trackSurface, centrePosition, axis1, sta # coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, vcd3[v]) # nodeIdentifier += 1 # for n3 in range(2): - # for n1 in range(elementsCountAroundVessel): + # 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]) @@ -528,15 +659,20 @@ def generateOstiumMesh(region, options, trackSurface, centrePosition, axis1, sta if vesselsCount == 1: mvNodeId[v] = oNodeId else: - os = 0 if (v == 0) else (nodesCountFree - 1) - of = os + nodesCountFree - mvNodeId[v] = [] + iv = max(0, v - 1) + mvNodeId[v] = [ None, None ] + oa = elementsCountAroundMid - iv*oinc + ob = elementsCountAroundMid + nodesCountFreeEnd - 1 + iv*oinc for n3 in range(2): - mvNodeId[v].append((oNodeId[n3]*2)[os:of]) - mvNodeId[v][n3] += reversed(xNodeId[n3]) if (v == 0) else xNodeId[n3] + 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 elements + # Create elementss ################# mesh = fm.findMeshByDimension(3) @@ -559,29 +695,37 @@ def generateOstiumMesh(region, options, trackSurface, centrePosition, axis1, sta 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 vessel to start from other side: + # reverse order of nodes around: for px in [ startPointsx, startPointsd1, startPointsd2, startPointsd3, startNodeId, startDerivativesMap, \ - endPointsx, endPointsd1, endPointsd2, endPointsd3, endNodeId, endDerivativesMap ]: + endPointsx, endPointsd1, endPointsd2, endPointsd3, endNodeId, endDerivativesMap ]: if px: for n3 in range(2): - px[n3] = px[n3][-elementsCountAcross::-1] + px[n3][:-elementsCountAcross:-1] + px[n3] = [ px[n3][0] ] + px[n3][len(px[n3]) - 1:0:-1] if vesselsCount > 1: - # must switch s1/s2 mapping to d1 around corners in startDerivativesMap - oa = elementsCountAroundVessel - elementsCountAcross + # must switch in and out xi1 maps around corners in startDerivativesMap for n3 in range(2): - startDerivativesMap[n3][0] = ( ( 0, -1, 0 ), ( 1, 1, 0 ), None, ( 1, 0, 0 )) - startDerivativesMap[n3][oa] = ( ( 1, 0, 0 ), ( -1, 1, 0 ), None, ( 0, 1, 0 )) + for n1 in range(elementsCountsAroundVessels[v]): + derivativesMap = startDerivativesMap[n3][n1] + if len(derivativesMap) == 4: + startDerivativesMap[n3][n1] = derivativesMap[3], derivativesMap[1], derivativesMap[2], derivativesMap[0] else: startPointsx, startPointsd1, startPointsd2, startPointsd3, startNodeId, startDerivativesMap = \ vox[v], vod1[v], vod2[v], vod3[v] if useCubicHermiteThroughVesselWall else None, None, None endPointsx, endPointsd1, endPointsd2, endPointsd3, endNodeId, endDerivativesMap = \ mvPointsx[v], mvPointsd1[v], mvPointsd2[v], mvPointsd3[v], mvNodeId[v], mvDerivativesMap[v] - nodeIdentifier, elementIdentifier = tricubichermite.createAnnulusMesh3d( + #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, - nodetemplate, nodetemplateLinearS3, nodeIdentifier, elementIdentifier, + forceMidLinearXi3 = not useCubicHermiteThroughVesselWall, elementsCountRadial = elementsCountAlong, meshGroups = vesselMeshGroups[v] if vesselMeshGroups else []) fm.endChange() - return annotationGroups, nodeIdentifier, elementIdentifier, (ox, od1, od2, od3, oNodeId) + return nodeIdentifier, elementIdentifier, (ox, od1, od2, od3, oNodeId, oPositions) diff --git a/scaffoldmaker/utils/annulusmesh.py b/scaffoldmaker/utils/annulusmesh.py new file mode 100644 index 00000000..356fbd1e --- /dev/null +++ b/scaffoldmaker/utils/annulusmesh.py @@ -0,0 +1,418 @@ +''' +Utility functions for generating annulus mesh between start and end loops of points. +''' +from __future__ import division +import copy +import math +from scaffoldmaker.utils.eftfactory_bicubichermitelinear import eftfactory_bicubichermitelinear +from scaffoldmaker.utils.eftfactory_tricubichermite import eftfactory_tricubichermite +from scaffoldmaker.utils.eft_utils import remapEftNodeValueLabel, setEftScaleFactorIds +from scaffoldmaker.utils import interpolation as interp +from scaffoldmaker.utils import vector +from scaffoldmaker.utils import zinc_utils +from opencmiss.zinc.element import Element +from opencmiss.zinc.field import Field +from opencmiss.zinc.node import Node + +def derivativeSignsToExpressionTerms(valueLabels, signs): + ''' + Return remap expression terms for summing derivative[i]*sign[i] + :param valueLabels: List of node value labels to possibly include. + :param signs: List of 1 (no scaling), -1 (scale by scale factor 1) or 0 (no term). + ''' + expressionTerms = [] + for i in range(len(valueLabels)): + if signs[i] is 1: + expressionTerms.append( ( valueLabels[i], [] ) ) + elif signs[i] is -1: + expressionTerms.append( ( valueLabels[i], [1] ) ) + return expressionTerms + +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 diff --git a/scaffoldmaker/utils/eftfactory_tricubichermite.py b/scaffoldmaker/utils/eftfactory_tricubichermite.py index 098c8f04..244cb7d6 100644 --- a/scaffoldmaker/utils/eftfactory_tricubichermite.py +++ b/scaffoldmaker/utils/eftfactory_tricubichermite.py @@ -11,20 +11,6 @@ from opencmiss.zinc.status import OK as ZINC_OK import math -def derivativeSignsToExpressionTerms(valueLabels, signs): - ''' - Return remap expression terms for summing derivative[i]*sign[i] - :param valueLabels: List of node value labels to possibly include. - :param signs: List of 1 (no scaling), -1 (scale by scale factor 1) or 0 (no term). - ''' - expressionTerms = [] - for i in range(len(valueLabels)): - if signs[i] is 1: - expressionTerms.append( ( valueLabels[i], [] ) ) - elif signs[i] is -1: - expressionTerms.append( ( valueLabels[i], [1] ) ) - return expressionTerms - class eftfactory_tricubichermite: ''' Factory class for creating element field templates for a 3-D mesh using tricubic Hermite basis. @@ -1208,339 +1194,3 @@ def replaceTwoElementWithInlet6(self, origElement1, origElement2, startElementId self._mesh.destroyElement(origElement1) self._mesh.destroyElement(origElement2) fm.endChange() - - def createAnnulusMesh3d(self, - startPointsx, startPointsd1, startPointsd2, startPointsd3, startNodeId, startDerivativesMap, - endPointsx, endPointsd1, endPointsd2, endPointsd3, endNodeId, endDerivativesMap, - nodetemplate, nodetemplateLinearS3, nextNodeIdentifier, nextElementIdentifier, - maxStartThickness = None, maxEndThickness = None, - elementsCountRadial = 1, meshGroups = []): - ''' - Create an annular 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 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. - :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. - :param nodetemplateLinearS3: Node template to use where linear through-thickness. - :param nextNodeIdentifier, nextElementIdentifier: Next identifiers to use and increment. - :param maxStartThickness, maxEndThickness: Optional maximum override on start/end thicknesses. - :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), 'eftfactory_tricubichermite.createAnnulusMesh3d: Invalid number of radial elements' - elementsCountWall = 1 - nodesCountWall = elementsCountWall + 1 - assert (len(startPointsx) == nodesCountWall) and (len(startPointsd1) == nodesCountWall) and (len(startPointsd2) == nodesCountWall) and \ - ((startPointsd3 is None) or (len(startPointsd3) == nodesCountWall)) and \ - (len(endPointsx) == nodesCountWall) and (len(endPointsd1) == nodesCountWall) and (len(endPointsd2) == nodesCountWall) and \ - ((endPointsd3 is None) 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)), \ - 'eftfactory_tricubichermite.createAnnulusMesh3d: Mismatch in number of layers through wall' - elementsCountAround = nodesCountAround = len(startPointsx[0]) - assert (nodesCountAround > 1), 'eftfactory_tricubichermite.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 \ - ((startPointsd3 is None) or (len(startPointsd3[n3]) == nodesCountAround)) and \ - (len(endPointsx[n3]) == nodesCountAround) and (len(endPointsd1[n3]) == nodesCountAround) and (len(endPointsd2[n3]) == nodesCountAround) and \ - ((endPointsd3 is None) 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)), \ - 'eftfactory_tricubichermite.createAnnulusMesh3d: Mismatch in number of points/nodes in layers through wall' - - fm = self._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): - for li in (px[n3], pd1[n3], pd2[n3], pd3[n3]): - li.insert(n2, [ 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 ] - 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 ] - 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] - - # scale derivatives pro-rata to give better sampling in-between - ds = interp.computeCubicHermiteDerivativeScaling(ax, ad2, bx, bd2) - scaledDerivatives = [ [ d*ds for d in d2 ] for d2 in [ ad2, bd2 ]] - #print('scaling', ds, [ ad2, bd2 ], 'vs', scaledDerivatives) - mx, md2, me, mxi = interp.sampleCubicHermiteCurves([ ax, bx ], scaledDerivatives, elementsCountRadial, - addLengthStart = 0.5*vector.magnitude(ad2), lengthFractionStart = 0.5, - addLengthEnd = 0.5*vector.magnitude(bd2), lengthFractionEnd = 0.5, arcLengthDerivatives = False)[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]) - - for n1 in range(nodesCountAround): - normal = vector.normalise(vector.crossproduct3(pd1[1][n2][n1], pd2[1][n2][n1])) - thickness = thicknesses[n2][n1] - pd3[0][n2][n1] = pd3[1][n2][n1] = [ d*thickness for d in normal ] - px [0][n2][n1] = [ (px [1][n2][n1][c] - pd3[1][n2][n1][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] ] - - # smooth derivative 1 around inner loop - pd1[0][n2] = interp.smoothCubicHermiteDerivativesLoop(px[0][n2], pd1[0][n2]) - - 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.ARITHMETIC_MEAN) # was HARMONIC_MEAN - for n2 in range(elementsCountRadial + 1): - pd2[n3][n2][n1] = sd2[n2] - - ############## - # Create nodes - ############## - - nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) - nodeIdentifier = nextNodeIdentifier - nodeId = [ [], [] ] - for n3 in range(2): - for n2 in range(elementsCountRadial + 1): - if (n2 == 0) and (startNodeId is not None): - rowNodeId = startNodeId[n3] - elif (n2 == elementsCountRadial) and (endNodeId is not None): - rowNodeId = endNodeId[n3] - else: - rowNodeId = [] - nodetemplate1 = nodetemplate if (pd3[n3][n2] is not None) 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 nodetemplate1 is nodetemplate: - coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, pd3[n3][n2][n1]) - nodeIdentifier = nodeIdentifier + 1 - nodeId[n3].append(rowNodeId) - - ################# - # Create elements - ################# - - elementIdentifier = nextElementIdentifier - - eftStandard = self.createEftNoCrossDerivatives() - - elementtemplate1 = self._mesh.createElementtemplate() - elementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE) - - for e2 in range(elementsCountRadial): - for e1 in range(elementsCountAround): - en = (e1 + 1)%elementsCountAround - mapStartDerivatives = (e2 == 0) and (startDerivativesMap is not None) - mapStartLinearDerivative3 = (e2 == 0) and (startPointsd3 is None) - mapEndDerivatives = (e2 == (elementsCountRadial - 1)) and (endDerivativesMap is not None) - mapEndLinearDerivative3 = (e2 == (elementsCountRadial - 1)) and (endPointsd3 is None) - - if mapStartDerivatives or mapStartLinearDerivative3 or mapEndDerivatives or mapEndLinearDerivative3: - eft1 = self.createEftNoCrossDerivatives() - setEftScaleFactorIds(eft1, [1], []) - if mapStartLinearDerivative3: - self.setEftLinearDerivative(eft1, [ 1, 5 ], Node.VALUE_LABEL_D_DS3, 1, 5, 1) - self.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 mapEndLinearDerivative3: - self.setEftLinearDerivative(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS3, 3, 7, 1) - self.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)) - else: - eft1 = eftStandard - - 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] ] - - elementtemplate1.defineField(coordinates, -1, eft1) - element = self._mesh.createElement(elementIdentifier, elementtemplate1) - result2 = element.setNodesByIdentifier(eft1, nids) - if eft1 is not eftStandard: - result3 = element.setScaleFactors(eft1, [ -1.0 ]) - else: - result3 = 7 - #print('create element orifice', element.isValid(), elementIdentifier, result2, result3, nids) - elementIdentifier += 1 - - for meshGroup in meshGroups: - meshGroup.addElement(element) - - fm.endChange() - - return nodeIdentifier, elementIdentifier diff --git a/scaffoldmaker/utils/geometry.py b/scaffoldmaker/utils/geometry.py index 59ae21c4..e19721b4 100644 --- a/scaffoldmaker/utils/geometry.py +++ b/scaffoldmaker/utils/geometry.py @@ -3,6 +3,7 @@ ''' from __future__ import division +import copy import math from scaffoldmaker.utils import vector @@ -16,6 +17,23 @@ def getApproximateEllipsePerimeter(a, b): h = ((a-b)/(a+b))**2 return math.pi*(a + b)*(1.0 + 3.0*h/(10.0 + math.sqrt(4.0 - 3.0*h))) +def getEllipseAngleFromVector(a, b, x, y): + ''' + Given an ellipse with major and minor axes a and b cented at (0.0, 0.0), + get angle in radians in direction of vector (dx, dy) + :param a: Major axis length. + :param b: Minor axis length. + :param x, y: Non-zero vector direction + :return: Angle in radians. + ''' + # Compute theta such that + # r*x = a*cos_theta + # r*y = b*sin_theta + # hence: + # y/x = b/a * tan(theta) + # (a*y)/(b*x) = tan(theta) + return math.atan2(a*y, b*x) + def getEllipseArcLength(a, b, angle1Radians, angle2Radians): ''' Calculates perimeter distance between two angles by summing line segments at regular angles. @@ -117,39 +135,47 @@ def createCirclePoints(cx, axis1, axis2, elementsCountAround, startRadians = 0.0 radiansAround += radiansPerElementAround return px, pd1 -def getCircleProjectionAxes(ax, ad1, ad2, ad3, angle1radians, angle2radians, length): +def getCircleProjectionAxes(ax, ad1, ad2, ad3, length, angle1radians, angle2radians, angle3radians = None): ''' - Project coordinates and orthogonal unit axes ax, ad1, ad2, ad3 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 are/must be length 3. - Assumes angles are not large e.g. less than 90 degrees. + 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) ] - return bx, ad1, ad2, ad3 - 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) + 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 getSurfaceProjectionAxes(ax, ad1, ad2, ad3, angle1radians, angle2radians, length): diff --git a/scaffoldmaker/utils/interpolation.py b/scaffoldmaker/utils/interpolation.py index 961ef66e..24086e05 100644 --- a/scaffoldmaker/utils/interpolation.py +++ b/scaffoldmaker/utils/interpolation.py @@ -223,6 +223,51 @@ def interpolateLagrangeHermiteDerivative(v1, v2, d2, xi): df3 = -1.0 + 2.0*xi return [ (v1[c]*df1 + v2[c]*df2 + d2[c]*df3) for c in range(len(v1)) ] +def getNearestPointIndex(nx, x): + ''' + :return: index of point in nx at shortest distance from x. + ''' + pointCount = len(nx) + assert pointCount > 0 + components = len(x) + minDistance = math.sqrt(sum( ((nx[0][c] - x[c])*(nx[0][c] - x[c])) for c in range(components))) + index = 0 + for n in range(1, pointCount): + distance = math.sqrt(sum( ((nx[n][c] - x[c])*(nx[n][c] - x[c])) for c in range(components))) + if distance < minDistance: + minDistance = distance + index = n + return index + +def projectHermiteCurvesThroughWall(nx, nd1, nd2, n, wallThickness): + ''' + 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 (n > 0) and (nx[n - 1]): + curvature += getCubicHermiteCurvature(nx[n - 1], nd1[n - 1], nx[n], nd1[n], unitNormal, 1.0) + count += 1 + if (n < maxPointIndex) and (nx[n + 1]): + curvature += getCubicHermiteCurvature(nx[n], nd1[n], nx[n + 1], nd1[n + 1], 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 + def sampleCubicHermiteCurves(nx, nd1, elementsCountOut, addLengthStart = 0.0, addLengthEnd = 0.0, lengthFractionStart = 1.0, lengthFractionEnd = 1.0, @@ -264,8 +309,11 @@ def sampleCubicHermiteCurves(nx, nd1, elementsCountOut, lengths.append(length) proportionEnd = 2.0/(elementLengthStartEndRatio + 1) proportionStart = elementLengthStartEndRatio*proportionEnd - elementLengthMid = (length - addLengthStart - addLengthEnd) / \ - (elementsCountOut - 2.0 + proportionStart*lengthFractionStart + proportionEnd*lengthFractionEnd) + if elementsCountOut == 1: + elementLengthMid = length + else: + elementLengthMid = (length - addLengthStart - addLengthEnd) / \ + (elementsCountOut - 2.0 + proportionStart*lengthFractionStart + proportionEnd*lengthFractionEnd) elementLengthProportionStart = proportionStart*lengthFractionStart*elementLengthMid elementLengthProportionEnd = proportionEnd*lengthFractionEnd*elementLengthMid # get smoothly varying element lengths, not accounting for start and end @@ -331,6 +379,78 @@ def sampleCubicHermiteCurves(nx, nd1, elementsCountOut, psf.append(sf) return px, pd1, pe, pxi, psf +def sampleCubicHermiteCurvesSmooth(nx, nd1, elementsCountOut, + derivativeMagnitudeStart, derivativeMagnitudeEnd): + """ + Get smoothly spaced points and derivatives over cubic Hermite interpolated + curves with nodes nx and derivatives nd1. The first element uses the first two nodes. + Gives smooth variation of element size to fit the supplied start and end derivative + magnitudes. + :param nx: Coordinates of nodes along curves. + :param nd1: Derivatives of nodes along curves. + :param derivativeMagnitudeStart, derivativeMagnitudeEnd: Magnitudes of start and end + derivatives appropriate for elementsCountOut. + :return: px[], pd1[], pe[], pxi[], psf[], where pe[] and pxi[] are lists of element indices and + and xi locations in the 'in' elements to pass to partner interpolateSample functions. psf[] is + a list of scale factors for converting derivatives from old to new xi coordinates: dxi(old)/dxi(new). + """ + elementsCountIn = len(nx) - 1 + assert (elementsCountIn > 0) and (len(nd1) == (elementsCountIn + 1)) and (elementsCountOut > 0), \ + 'sampleCubicHermiteCurvesSmooth. Invalid arguments' + lengths = [ 0.0 ] + length = 0.0 + for e in range(elementsCountIn): + length += getCubicHermiteArcLength(nx[e], nd1[e], nx[e + 1], nd1[e + 1]) + lengths.append(length) + # sample over length to get distances to elements boundaries + x1 = 0.0 + d1 = derivativeMagnitudeStart*elementsCountOut + x2 = length + d2 = derivativeMagnitudeEnd*elementsCountOut + nodeDistances = [] + nodeDerivativeMagnitudes = [] + for n in range(elementsCountOut + 1): + xi = n/elementsCountOut + f1, f2, f3, f4 = getCubicHermiteBasis(xi) + distance = f1*x1 + f2*d1 + f3*x2 + f4*d2 + nodeDistances.append(distance) + f1, f2, f3, f4 = getCubicHermiteBasisDerivatives(xi) + derivative = f1*x1 + f2*d1 + f3*x2 + f4*d2 + nodeDerivativeMagnitudes.append(derivative/elementsCountOut) + #print('nodeDerivativeMagnitudesIn ', [ vector.magnitude(d1) for d1 in nd1 ]) + #print('nodeDerivativeMagnitudesOut', nodeDerivativeMagnitudes) + px = [] + pd1 = [] + pe = [] + pxi = [] + psf = [] + e = 0 + for eOut in range(elementsCountOut): + distance = nodeDistances[eOut] + while e < elementsCountIn: + if distance < lengths[e + 1]: + partDistance = distance - lengths[e] + x, d1, _, xi = getCubicHermiteCurvesPointAtArcDistance(nx[e:e + 2], nd1[e:e + 2], partDistance) + sf = nodeDerivativeMagnitudes[eOut]/vector.magnitude(d1) + px.append(x) + pd1.append([ sf*d for d in d1 ]) + pe.append(e) + pxi.append(xi) + psf.append(sf) + break + e += 1 + e = elementsCountIn + eOut = elementsCountOut + xi = 1.0 + d1 = nd1[e] + sf = nodeDerivativeMagnitudes[eOut]/vector.magnitude(d1) + px.append(nx[e]) + pd1.append([ sf*d for d in d1 ]) + pe.append(e - 1) + pxi.append(xi) + psf.append(sf) + return px, pd1, pe, pxi, psf + def interpolateSampleCubicHermite(v, d, pe, pxi, psf): ''' Partner function to sampleCubicHermiteCurves for interpolating additional variables with @@ -401,6 +521,34 @@ def interpolateSampleLinear(v, pe, pxi): vOut.append(wm*v[pe[n]] + wp*v[pe[n] + 1]) return vOut +def sampleCubicElementLengths(length, elementsCount, startDerivative = None, endDerivative = None): + ''' + Get lengths of elements gradually changing over length, satisfying the start and end derivatives. + :param startDerivative, endDerivative: Magnitudes of end derivatives to use with elementsCount, + or None to choose a natural size. + :return: List of elementsCount element lengths + ''' + assert (elementsCount > 0), 'interpolation sampleCubicElementLengths: Invalid number of elements' + x1 = 0.0 + x2 = length + d1 = startDerivative*elementsCount if startDerivative else None + d2 = endDerivative*elementsCount if endDerivative else None + if not (d1 and d2): + d1 = d2 = length + elif not d2: + d2 = 2.0*length - d1 + elif not d1: + d1 = 2.0*length - d2 + elementLengths = [] + lastx = 0.0 + for n in range(1, elementsCount + 1): + xi = n/elementsCount + f1, f2, f3, f4 = getCubicHermiteBasis(xi) + x = f1*x1 + f2*d1 + f3*x2 + f4*d2 + elementLengths.append(x - lastx) + lastx = x + return elementLengths + def getCubicHermiteCurvesPointAtArcDistance(nx, nd, arcDistance): """ Get the coordinates, derivatives at distance along cubic Hermite curves. @@ -436,6 +584,8 @@ def getCubicHermiteCurvesPointAtArcDistance(nx, nd, arcDistance): dist = getCubicHermiteArcLengthToXi(v1, d1, v2, d2, xi) distp = getCubicHermiteArcLengthToXi(v1, d1, v2, d2, xi + xiDelta) distm = getCubicHermiteArcLengthToXi(v1, d1, v2, d2, xi - xiDelta) + if (xi - xiDelta) < 0.0: + distm = -distm dxi_ddist = 2.0*xiDelta/(distp - distm) dxi = dxi_ddist*(partDistance - dist) #print('iter',iter,'xi',xi,'--> dist',dist,'dxi',dxi,'dxiLimit',dxiLimit) diff --git a/scaffoldmaker/utils/meshrefinement.py b/scaffoldmaker/utils/meshrefinement.py index 2c2d7b30..4d533a5c 100644 --- a/scaffoldmaker/utils/meshrefinement.py +++ b/scaffoldmaker/utils/meshrefinement.py @@ -2,6 +2,7 @@ Class for refining a mesh from one region to another. ''' from __future__ import division +import math from scaffoldmaker.annotation.annotationgroup import AnnotationGroup from scaffoldmaker.utils.octree import Octree from scaffoldmaker.utils import zinc_utils diff --git a/scaffoldmaker/utils/tracksurface.py b/scaffoldmaker/utils/tracksurface.py index 92d47f2f..ef12317f 100644 --- a/scaffoldmaker/utils/tracksurface.py +++ b/scaffoldmaker/utils/tracksurface.py @@ -32,6 +32,28 @@ def __init__(self, elementsCount1, elementsCount2, nx, nd1, nd2): self.nd1 = nd1 self.nd2 = nd2 + def createMirrorX(self): + ''' + Mirror track surface about x axis by negating all x coordinates + and rewind elements by flipping order and derivatives in direction 1. + :return: TrackSurface + ''' + nx = [] + nd1 = [] + nd2 = [] + nodesCount1 = self.elementsCount1 + 1 + nodesCount2 = self.elementsCount2 + 1 + for n2 in range(nodesCount2): + for n1 in range(nodesCount1): + oi = n2*nodesCount1 + self.elementsCount1 - n1 + ox = copy.deepcopy(self.nx [oi]) + od1 = copy.deepcopy(self.nd1[oi]) + od2 = copy.deepcopy(self.nd2[oi]) + nx .append([ -ox [0], ox [1], ox [2] ]) + nd1.append([ od1[0], -od1[1], -od1[2] ]) + nd2.append([ -od2[0], od2[1], od2[2] ]) + return TrackSurface(self.elementsCount1, self.elementsCount2, nx, nd1, nd2) + def createPositionProportion(self, proportion1, proportion2): ''' Return position on surface for proportions across directions 1 and 2. @@ -39,24 +61,32 @@ def createPositionProportion(self, proportion1, proportion2): each varying from 0.0 to 1.0, with elements equal sized. :return: TrackSurfacePosition ''' - assert (proportion1 >= 0.0) and (proportion1 <= 1.0) and (proportion2 >= 0.0) and (proportion2 <= 1.0), 'Proportion out of range' + assert (proportion1 >= 0.0) and (proportion1 <= 1.0) and (proportion2 >= 0.0) and (proportion2 <= 1.0), \ + 'createPositionProportion: Proportion out of range' pe1 = proportion1*self.elementsCount1 if pe1 < self.elementsCount1: e1 = int(pe1) xi1 = pe1 - e1 else: - e1 = self.elementsCount1 + e1 = self.elementsCount1 - 1 xi1 = 1.0 pe2 = proportion2*self.elementsCount2 if pe2 < self.elementsCount2: e2 = int(pe2) xi2 = pe2 - e2 else: - e2 = self.elementsCount2 + e2 = self.elementsCount2 - 1 xi2 = 1.0 return TrackSurfacePosition(e1, e2, xi1, xi2) - def evaluateCoordinates(self, position, derivatives=False): + def getProportion(self, position): + ''' + From a position on this track surface, return proportions. + :return: proportion1, proportion2 + ''' + return [ (position.e1 + position.xi1)/self.elementsCount1, (position.e2 + position.xi2)/self.elementsCount2 ] + + def evaluateCoordinates(self, position, derivatives = False): ''' Evaluate coordinates on surface at position, and optionally derivative w.r.t. xi1 and xi2. @@ -107,6 +137,142 @@ def evaluateCoordinates(self, position, derivatives=False): derivative2.append(d2) return coordinates, derivative1, derivative2 + def createHermiteCurvePoints(self, aProportion1, aProportion2, bProportion1, bProportion2, elementsCount, + derivativeStart = None, derivativeEnd = None): + ''' + 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 + 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) + #print('addLengthStart', addLengthStart, 'lengthFractionStart', lengthFractionStart) + 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 + dp2End *= elementsCount + #print('end delta_xi1', delta_xi1, 'delta_xi2', delta_xi2) + #print('dp1End', dp1End, 'dp2End', dp2End) + #print('addLengthEnd', addLengthEnd, 'lengthFractionEnd', lengthFractionEnd) + 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 ], 0.0) + else: + dp1End = bProportion1 - aProportion1 + dp2End = bProportion2 - aProportion2 + derivativeMagnitudeEnd = math.sqrt(dp1End*dp1End + dp2End*dp2End)/elementsCount + proportions, dproportions = interp.sampleCubicHermiteCurvesSmooth([ [ aProportion1, aProportion2 ], [ bProportion1, bProportion2 ] ], \ + [ [ dp1Start, dp2Start ], [ dp1End, dp2End ] ], elementsCount, derivativeMagnitudeStart, derivativeMagnitudeEnd)[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 + 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 findNearestPosition(self, targetx, startPosition = None): + ''' + Find the nearest point to targetx on the track surface, with optional start position. + Only works if track surface is simply shaped; use a close startPosition if not. + :return: Nearest TrackSurfacePosition + ''' + if not startPosition: + startPosition = self.createPositionProportion(0.5, 0.5) + position = copy.deepcopy(startPosition) + max_mag_dxi = 0.5 # target/maximum magnitude of xi increment + xi_tol = 1.0E-6 + for iter in range(100): + xi1 = position.xi1 + xi2 = position.xi2 + ax, ad1, ad2 = self.evaluateCoordinates(position, derivatives = True) + deltax = [ (targetx[c] - ax[c]) for c in range(3) ] + #print('iter', iter + 1, 'position', position, 'deltax', deltax, 'err', vector.magnitude(deltax)) + adelta_xi1, adelta_xi2 = calculate_surface_delta_xi(ad1, ad2, deltax) + mag_dxi = math.sqrt(adelta_xi1*adelta_xi1 + adelta_xi2*adelta_xi2) + dxi1 = adelta_xi1 + dxi2 = adelta_xi2 + if mag_dxi > max_mag_dxi: + dxi1 *= max_mag_dxi/mag_dxi + dxi2 *= max_mag_dxi/mag_dxi + #print(' dxi', dxi1, dxi2) + bxi1, bxi2, proportion, faceNumber = increment_xi_on_square(xi1, xi2, dxi1, dxi2) + position.xi1 = bxi1 + position.xi2 = bxi2 + if mag_dxi < xi_tol: + #print('converged mag_dxi', mag_dxi) + break + if faceNumber: + onBoundary = self.updatePositionTofaceNumber(position, faceNumber) + if onBoundary and (proportion < xi_tol): + # slide along boundary to nearest point; may cross other sides + if faceNumber in [1, 2]: + bdxi1 = 0.0 + cmag_dxi = bdxi2 = dxi2*(1.0 - proportion) + else: + cmag_dxi = bdxi1 = dxi1*(1.0 - proportion) + bdxi2 = 0.0 + cxi1, cxi2, cproportion, cFaceNumber = increment_xi_on_square(bxi1, bxi2, bdxi1, bdxi2) + position.xi1 = cxi1 + position.xi2 = cxi2 + if math.fabs(cmag_dxi) < xi_tol: + #print('converged on boundary cmag_dxi', cmag_dxi) + break + if cFaceNumber: + cOnBoundary = self.updatePositionTofaceNumber(position, cFaceNumber) + if cOnBoundary: + #print('TrackSurface.findNearestPosition: End on corner boundary at', position) + break + else: + print('TrackSurface.findNearestPosition: Reach max iterations', iter + 1, 'closeness in xi', mag_dxi) + #print('final position', position) + return position + def trackVector(self, startPosition, direction, trackDistance): ''' Track from startPosition the given distance in the vector direction. @@ -116,6 +282,7 @@ def trackVector(self, startPosition, direction, trackDistance): :param trackDistance: Distance to track along. Can be negative. :return: Final TrackSurfacePosition ''' + #print('TrackSurface.trackVector start position', startPosition, 'direction', direction, 'distance', trackDistance) useDirection = direction useTrackDistance = trackDistance if trackDistance < 0.0: @@ -131,9 +298,11 @@ def trackVector(self, startPosition, direction, trackDistance): xi2 = position.xi2 ax, ad1, ad2 = self.evaluateCoordinates(position, derivatives = True) adelta_xi1, adelta_xi2 = calculate_surface_delta_xi(ad1, ad2, useDirection) + #print('adelta_xi', adelta_xi1, adelta_xi2) scale = max_mag_dxi/math.sqrt(adelta_xi1*adelta_xi1 + adelta_xi2*adelta_xi2) adxi1 = dxi1 = scale*adelta_xi1 adxi2 = dxi2 = scale*adelta_xi2 + #print('adxi', adxi1, adxi2) for i in range(1): # can try more in future # can go slightly outside element to get predictor/correct position position.xi1 = xi1 + dxi1 @@ -155,6 +324,7 @@ def trackVector(self, startPosition, direction, trackDistance): scale = max_mag_dxi/math.sqrt(bdelta_xi1*bdelta_xi1 + bdelta_xi2*bdelta_xi2) bdxi1 = scale*bdelta_xi1 bdxi2 = scale*bdelta_xi2 + #print('bdxi', bdxi1, bdxi2) ad = [ proportion*(adxi1*ad1[c] + adxi2*ad2[c]) for c in range(3) ] bd = [ proportion*(bdxi1*bd1[c] + bdxi2*bd2[c]) for c in range(3) ] # GRC check: @@ -168,42 +338,57 @@ def trackVector(self, startPosition, direction, trackDistance): position.xi2 = xi2 + r*dxi2 #print(distance, '-->', position, '(final)') break - if arcLength == 0.0: + if (arcLength == 0.0) and (not faceNumber): print('TrackSurface.trackVector. No increment at', position, 'final distance', distance, 'of', useTrackDistance) break distance += arcLength if faceNumber: - onBoundary = False - if faceNumber == 1: # xi1 == 0.0 - if position.e1 > 0: - position.e1 -= 1 - position.xi1 = 1.0 - else: - onBoundary = True - if faceNumber == 2: # xi1 == 1.0 - if position.e1 < (self.elementsCount1 - 2): - position.e1 += 1 - position.xi1 = 0.0 - else: - onBoundary = True - if faceNumber == 3: # xi2 == 0.0 - if position.e2 > 0: - position.e2 -= 1 - position.xi2 = 1.0 - else: - onBoundary = True - if faceNumber == 4: # xi2 == 1.0 - if position.e2 < (self.elementsCount2 - 2): - position.e2 += 1 - position.xi2 = 0.0 - else: - onBoundary = True + onBoundary = self.updatePositionTofaceNumber(position, faceNumber) if onBoundary: print('TrackSurface.trackVector: End on boundary at', position) break #print(' cross face', faceNumber, 'new position', position) return position + def updatePositionTofaceNumber(self, position, faceNumber): + ''' + Update coordinates of TrackSurfacePosition position to cross + the given face number, or clamp to range if reached boundary. + :return: True if reached boundary of track surface, otherwise False. + ''' + onBoundary = False + if faceNumber == 1: # xi1 == 0.0 + if position.e1 > 0: + position.e1 -= 1 + position.xi1 = 1.0 + else: + position.xi1 = 0.0 + onBoundary = True + elif faceNumber == 2: # xi1 == 1.0 + if position.e1 < (self.elementsCount1 - 1): + position.e1 += 1 + position.xi1 = 0.0 + else: + position.xi1 = 1.0 + onBoundary = True + elif faceNumber == 3: # xi2 == 0.0 + if position.e2 > 0: + position.e2 -= 1 + position.xi2 = 1.0 + else: + position.xi2 = 0.0 + onBoundary = True + elif faceNumber == 4: # xi2 == 1.0 + if position.e2 < (self.elementsCount2 - 1): + position.e2 += 1 + position.xi2 = 0.0 + else: + position.xi2 = 1.0 + onBoundary = True + #if onBoundary: + # print('!!! Reached boundary of face', faceNumber, 'position', position) + return onBoundary + class TrackSurfacePosition: ''' @@ -218,7 +403,7 @@ def __init__(self, e1, e2, xi1, xi2): self.e1 = e1 self.e2 = e2 self.xi1 = xi1 - self.xi2 = xi1 + self.xi2 = xi2 def __str__(self): return 'element (' + str(self.e1) + ',' + str(self.e2) + ') xi (' + str(self.xi1) + ',' + str(self.xi2) + ')' @@ -246,6 +431,8 @@ def calculate_surface_delta_xi(d1, d2, direction): inva = [ [ a[1][1]/deta, -a[0][1]/deta ], [ -a[1][0]/deta, a[0][0]/deta ] ] delta_xi1 = inva[0][0]*b[0] + inva[0][1]*b[1] delta_xi2 = inva[1][0]*b[0] + inva[1][1]*b[1] + delx = [ (delta_xi1*d1[c] + delta_xi2*d2[c]) for c in range(3) ] + #print('delx', delx, 'dir', direction, 'diff', vector.magnitude([ (delx[c] - direction[c]) for c in range(3) ])) return delta_xi1, delta_xi2 def calculate_surface_axes(d1, d2, direction): @@ -263,40 +450,42 @@ def calculate_surface_axes(d1, d2, direction): def increment_xi_on_square(xi1, xi2, dxi1, dxi2): ''' Increment xi1, xi2 by dxi1, dxi2 limited to square element bounds on [0,1]. + Works out face crossed first and limits that xi to 0.0 or 1.0 and other xi + in proportion. :return: New xi1, xi2, proportion, face number 1-4 or None if within boundary. Proportion is 1.0 if increment is within element, otherwise equal to proportion of dxi1, dxi2 incremented to hit the boundary. If so limited to the boundary, the face number is returned as an integer: - 1 is xi1=0.0, 2 is xi1=1.0, 3 is xi2=0.0, 4 is xi2=1.0 + 1 is xi1==0.0, 2 is xi1==1.0, 3 is xi2==0.0, 4 is xi2==1.0 ''' - nxi1 = xi1 + dxi1 - nxi2 = xi2 + dxi2 + onxi1 = nxi1 = xi1 + dxi1 + onxi2 = nxi2 = xi2 + dxi2 proportion = 1.0 faceNumber = None if (nxi1 < 0.0) or (nxi1 > 1.0) or (nxi2 < 0.0) or (nxi2 > 1.0): # come back in direction of dxi1, dxi2 to first boundary - if (nxi1 < 0.0) and (dxi1 < 0.0): + if (onxi1 < 0.0) and (dxi1 < 0.0): thisProportion = -xi1/dxi1 if thisProportion < proportion: proportion = thisProportion faceNumber = 1 nxi1 = 0.0 - nix2 = xi2 + proportion*dxi2 - if (nxi1 > 1.0) and (dxi1 > 0.0): + nxi2 = xi2 + proportion*dxi2 + elif (onxi1 > 1.0) and (dxi1 > 0.0): thisProportion = (1.0 - xi1)/dxi1 if thisProportion < proportion: proportion = thisProportion faceNumber = 2 nxi1 = 1.0 - nix2 = xi2 + proportion*dxi2 - if (nxi2 < 0.0) and (dxi2 < 0.0): + nxi2 = xi2 + proportion*dxi2 + if (onxi2 < 0.0) and (dxi2 < 0.0): thisProportion = -xi2/dxi2 if thisProportion < proportion: proportion = thisProportion faceNumber = 3 nxi1 = xi1 + proportion*dxi1 nxi2 = 0.0 - if (nxi2 > 1.0) and (dxi2 > 0.0): + elif (onxi2 > 1.0) and (dxi2 > 0.0): thisProportion = (1.0 - xi2)/dxi2 if thisProportion < proportion: proportion = thisProportion