diff --git a/src/scaffoldmaker/annotation/lung_terms.py b/src/scaffoldmaker/annotation/lung_terms.py new file mode 100644 index 00000000..9b8e7df3 --- /dev/null +++ b/src/scaffoldmaker/annotation/lung_terms.py @@ -0,0 +1,27 @@ +""" +Common resource for lungs annotation terms. +""" + +# convention: preferred name, preferred id, followed by any other ids and alternative names +lung_terms = [ + ("lung", "UBERON:0002048", "ILX:0726937"), + ("left lung", "UBERON:0002168", "ILX:0733450"), + ("right lung", "UBERON:0002167", "ILX:0729582"), + ("upper lobe of left lung", "UBERON:0008952", "ILX:0735339"), + ("lower lobe of left lung", "UBERON:0008953", "ILX:0735534"), + ("upper lobe of right lung", "UBERON:0002170", "ILX:0728821"), + ("middle lobe of right lung", "UBERON:0002174", "ILX:0733737"), + ("lower lobe of right lung", "UBERON:0002171", "ILX:0725712"), + ("right lung accessory lobe", "UBERON:0004890", "ILX:0728696") +] + +def get_lung_term(name : str): + """ + Find term by matching name to any identifier held for a term. + Raise exception if name not found. + :return ( preferred name, preferred id ) + """ + for term in lung_terms: + if name in term: + return ( term[0], term[1] ) + raise NameError("Lung annotation term '" + name + "' not found.") diff --git a/src/scaffoldmaker/meshtypes/meshtype_3d_lung1.py b/src/scaffoldmaker/meshtypes/meshtype_3d_lung1.py new file mode 100644 index 00000000..c8945587 --- /dev/null +++ b/src/scaffoldmaker/meshtypes/meshtype_3d_lung1.py @@ -0,0 +1,219 @@ +''' +Generates 3D lung surface mesh. +''' + +from scaffoldmaker.annotation.annotationgroup import AnnotationGroup +from scaffoldmaker.annotation.lung_terms import get_lung_term +from scaffoldmaker.meshtypes.scaffold_base import Scaffold_base +from scaffoldmaker.utils.eftfactory_tricubichermite import eftfactory_tricubichermite +from opencmiss.utils.zinc.field import findOrCreateFieldCoordinates, findOrCreateFieldGroup, \ + findOrCreateFieldNodeGroup, findOrCreateFieldStoredMeshLocation, findOrCreateFieldStoredString +from opencmiss.zinc.element import Element +from opencmiss.zinc.field import Field +from opencmiss.zinc.node import Node + + +class MeshType_3d_lung1(Scaffold_base): + ''' + 3D lung scaffold. + ''' + + @staticmethod + def getName(): + return '3D Lung 1' + + @staticmethod + def getParameterSetNames(): + return [ + 'Default', + 'Mouse 1'] + + @classmethod + def getDefaultOptions(cls, parameterSetName='Default'): + options = {} + options['Base parameter set'] = parameterSetName + + return options + + @staticmethod + def getOrderedOptionNames(): + optionNames = [] + return optionNames + + @classmethod + def generateBaseMesh(cls, region, options): + ''' + Generate the base tricubic Hermite mesh. See also generateMesh(). + :param region: Zinc region to define model in. Must be empty. + :param options: Dict containing options. See getDefaultOptions(). + :return: annotationGroups + ''' + fm = region.getFieldmodule() + coordinates = findOrCreateFieldCoordinates(fm) + + nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) + nodetemplate = nodes.createNodetemplate() + nodetemplate.defineField(coordinates) + nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_VALUE, 1) + nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS1, 1) + nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS2, 1) + nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS3, 1) + + mesh = fm.findMeshByDimension(3) + cache = fm.createFieldcache() + + elementsCount1 = 2 + elementsCount2 = 4 + elementsCount3 = 4 + + # Annotation fiducial point + markerGroup = findOrCreateFieldGroup(fm, "marker") + markerName = findOrCreateFieldStoredString(fm, name="marker_name") + markerLocation = findOrCreateFieldStoredMeshLocation(fm, mesh, name="marker_location") + + nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) + markerPoints = findOrCreateFieldNodeGroup(markerGroup, nodes).getNodesetGroup() + markerTemplateInternal = nodes.createNodetemplate() + markerTemplateInternal.defineField(markerName) + markerTemplateInternal.defineField(markerLocation) + + # Create nodes + nodeIdentifier = 1 + lNodeIds = [] + d1 = [0.5, 0.0, 0.0] + d2 = [0.0, 0.5, 0.0] + d3 = [0.0, 0.0, 1.0] + for n3 in range(elementsCount3 + 1): + lNodeIds.append([]) + for n2 in range(elementsCount2 + 1): + lNodeIds[n3].append([]) + for n1 in range(elementsCount1 + 1): + lNodeIds[n3][n2].append([]) + if n3 < elementsCount3: + if (n1 == 0) and ((n2 == 0) or (n2 == elementsCount2)): + continue + else: + if (n2 == 0) or (n2 == elementsCount2) or (n1 == 0): + continue + node = nodes.createNode(nodeIdentifier, nodetemplate) + cache.setNode(node) + x = [0.5 * (n1 - 1), 0.5 * (n2 - 1), 1.0 * n3] + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, x) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, d1) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, d2) + coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, d3) + lNodeIds[n3][n2][n1] = nodeIdentifier + nodeIdentifier += 1 + + # Create elements + mesh = fm.findMeshByDimension(3) + eftfactory = eftfactory_tricubichermite(mesh, None) + eftRegular = eftfactory.createEftBasic() + + elementtemplateRegular = mesh.createElementtemplate() + elementtemplateRegular.setElementShapeType(Element.SHAPE_TYPE_CUBE) + elementtemplateRegular.defineField(coordinates, -1, eftRegular) + + elementtemplateCustom = mesh.createElementtemplate() + elementtemplateCustom.setElementShapeType(Element.SHAPE_TYPE_CUBE) + + lungGroup = AnnotationGroup(region, get_lung_term("lung")) + leftLungGroup = AnnotationGroup(region, get_lung_term("left lung")) + rightLungGroup = AnnotationGroup(region, get_lung_term("right lung")) + annotationGroups = [leftLungGroup, rightLungGroup, lungGroup] + + lungMeshGroup = lungGroup.getMeshGroup(mesh) + leftLungMeshGroup = leftLungGroup.getMeshGroup(mesh) + rightLungMeshGroup = rightLungGroup.getMeshGroup(mesh) + + eft1 = eftfactory.createEftWedgeCollapseXi1Quadrant([1, 5]) + eft2 = eftfactory.createEftWedgeCollapseXi1Quadrant([3, 7]) + eft3 = eftfactory.createEftWedgeCollapseXi2Quadrant([5, 6]) + eft4 = eftfactory.createEftWedgeCollapseXi2Quadrant([7, 8]) + eft5 = eftfactory.createEftWedgeCollapseXi1Quadrant([5, 7]) + eft6 = eftfactory.createEftTetrahedronCollapseXi1Xi2Quadrant(8, 2) + eft7 = eftfactory.createEftTetrahedronCollapseXi1Xi2Quadrant(6, 3) + + elementIdentifier = 1 + for e3 in range(elementsCount3): + for e2 in range(elementsCount2): + for e1 in range(elementsCount1): + eft = eftRegular + nodeIdentifiers = [ + lNodeIds[e3 ][e2][e1], lNodeIds[e3 ][e2][e1 + 1], lNodeIds[e3 ][e2 + 1][e1], lNodeIds[e3 ][e2 + 1][e1 + 1], + lNodeIds[e3 + 1][e2][e1], lNodeIds[e3 + 1][e2][e1 + 1], lNodeIds[e3 + 1][e2 + 1][e1], lNodeIds[e3 + 1][e2 + 1][e1 + 1]] + scalefactors = None + if (e3 < elementsCount3 - 1): + if (e2 == 0) and (e1 == 0): + # Back wedge elements + nodeIdentifiers.pop(4) + nodeIdentifiers.pop(0) + eft = eft1 + scalefactors = [-1.0] + elif (e2 == elementsCount2 - 1) and (e1 == 0): + # Front wedge elements + nodeIdentifiers.pop(6) + nodeIdentifiers.pop(2) + eft = eft2 + else: + if (e2 == 0) and (e1 == 1): + # Top back wedge elements + nodeIdentifiers.pop(5) + nodeIdentifiers.pop(4) + eft = eft3 + elif (e2 == elementsCount2 - 1) and (e1 == 1): + # Top front wedge elements + nodeIdentifiers.pop(7) + nodeIdentifiers.pop(6) + eft = eft4 + scalefactors = [-1.0] + elif (e2 == 1) and (e1 == 0): + # Top middle back wedge element + nodeIdentifiers.pop(6) + nodeIdentifiers.pop(4) + eft = eft5 + elif (e2 == 2) and (e1 == 0): + # Top middle front wedge element + nodeIdentifiers.pop(6) + nodeIdentifiers.pop(4) + eft = eft5 + if (e2 == 0) and (e1 == 0): + # Top back tetrahedron element + nodeIdentifiers.pop(6) + nodeIdentifiers.pop(5) + nodeIdentifiers.pop(4) + nodeIdentifiers.pop(0) + eft = eft6 + scalefactors = [-1.0] + if (e2 == elementsCount2 - 1) and (e1 == 0): + # Top front tetrahedron element + nodeIdentifiers.pop(7) + nodeIdentifiers.pop(6) + nodeIdentifiers.pop(4) + nodeIdentifiers.pop(2) + eft = eft7 + scalefactors = [-1.0] + + if eft is eftRegular: + element = mesh.createElement(elementIdentifier, elementtemplateRegular) + else: + elementtemplateCustom.defineField(coordinates, -1, eft) + element = mesh.createElement(elementIdentifier, elementtemplateCustom) + element.setNodesByIdentifier(eft, nodeIdentifiers) + if scalefactors: + element.setScaleFactors(eft, scalefactors) + elementIdentifier += 1 + leftLungMeshGroup.addElement(element) + lungMeshGroup.addElement(element) + + # Apex annotation point + idx = elementsCount1 * elementsCount2 * (elementsCount3 - 1) + elementsCount1 * (elementsCount2 // 2) + element1 = mesh.findElementByIdentifier(idx) + markerPoint = markerPoints.createNode(nodeIdentifier, markerTemplateInternal) + nodeIdentifier += 1 + cache.setNode(markerPoint) + markerName.assignString(cache, 'apex of left lung') + markerLocation.assignMeshLocation(cache, element1, [1.0, 1.0, 1.0]) + + return annotationGroups + diff --git a/src/scaffoldmaker/scaffolds.py b/src/scaffoldmaker/scaffolds.py index b523cde6..26432774 100644 --- a/src/scaffoldmaker/scaffolds.py +++ b/src/scaffoldmaker/scaffolds.py @@ -29,6 +29,7 @@ from scaffoldmaker.meshtypes.meshtype_3d_heartventriclesbase1 import MeshType_3d_heartventriclesbase1 from scaffoldmaker.meshtypes.meshtype_3d_heartventriclesbase2 import MeshType_3d_heartventriclesbase2 from scaffoldmaker.meshtypes.meshtype_3d_lens1 import MeshType_3d_lens1 +from scaffoldmaker.meshtypes.meshtype_3d_lung1 import MeshType_3d_lung1 from scaffoldmaker.meshtypes.meshtype_3d_ostium1 import MeshType_3d_ostium1 from scaffoldmaker.meshtypes.meshtype_3d_smallintestine1 import MeshType_3d_smallintestine1 from scaffoldmaker.meshtypes.meshtype_3d_solidsphere1 import MeshType_3d_solidsphere1 @@ -72,6 +73,7 @@ def __init__(self): MeshType_3d_heartventriclesbase1, MeshType_3d_heartventriclesbase2, MeshType_3d_lens1, + MeshType_3d_lung1, MeshType_3d_ostium1, MeshType_3d_smallintestine1, MeshType_3d_solidsphere1, diff --git a/src/scaffoldmaker/utils/eftfactory_tricubichermite.py b/src/scaffoldmaker/utils/eftfactory_tricubichermite.py index 02e26ba0..91b0442c 100644 --- a/src/scaffoldmaker/utils/eftfactory_tricubichermite.py +++ b/src/scaffoldmaker/utils/eftfactory_tricubichermite.py @@ -819,6 +819,201 @@ def createEftWedgeXi1Zero(self): assert eft.validate(), 'eftfactory_tricubichermite.createEftWedgeXi1Zero: Failed to validate eft' return eft + def createEftWedgeCollapseXi1Quadrant(self, collapseNodes): + ''' + Create a tricubic hermite element field for a wedge element, where xi1 collapsed on xi3 = 0 or xi3 = 1. + :return: Element field template + ''' + eft = self.createEftBasic() + setEftScaleFactorIds(eft, [1], []) + + valid = True + if collapseNodes in [[1, 3], [2, 4]]: + nodes = [1, 2, 3, 4] + # remap parameters on xi3 = 0 before collapsing nodes + if collapseNodes == [1, 3]: + remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS1, [1])]) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, []) + elif collapseNodes == [2, 4]: + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, []) + remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS1, [])]) + else: + valid = False + ln_map = [1, 1, 2, 2, 3, 4, 5, 6] + elif collapseNodes in [[5, 7], [6, 8]]: + nodes = [5, 6, 7, 8] + # remap parameters on xi3 = 1 before collapsing nodes + if collapseNodes == [5, 7]: + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, []) + remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS1, [])]) + elif collapseNodes == [6, 8]: + remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS1, [1])]) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, []) + else: + valid = False + ln_map = [1, 2, 3, 4, 5, 5, 6, 6] + elif collapseNodes in [[1, 5], [2, 6]]: + nodes = [1, 2, 5, 6] + # remap parameters on xi2 = 0 before collapsing nodes + if collapseNodes == [1, 5]: + remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [1])]) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, []) + elif collapseNodes == [2, 6]: + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, []) + remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [])]) + else: + valid = False + ln_map = [1, 1, 2, 3, 4, 4, 5, 6] + elif collapseNodes in [[3, 7], [4, 8]]: + nodes = [3, 4, 7, 8] + # remap parameters on xi2 = 1 before collapsing nodes + if collapseNodes == [3, 7]: + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, []) + remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [])]) + elif collapseNodes == [4, 8]: + remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [1])]) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, []) + else: + valid = False + ln_map = [1, 2, 3, 3, 4, 5, 6, 6] + else: + valid = False + + if not valid: + assert False, "createEftWedgeCollapseXi1Quadrant. Not implemented for collapse nodes " + str(collapseNodes) + + # zero cross derivative parameters + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D2_DS1DS2, []) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D2_DS1DS3, []) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D2_DS2DS3, []) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D3_DS1DS2DS3, []) + + remapEftLocalNodes(eft, 6, ln_map) + assert eft.validate(), 'eftfactory_tricubichermite.createEftWedgeCollapseXi1Quadrant: Failed to validate eft' + return eft + + def createEftWedgeCollapseXi2Quadrant(self, collapseNodes): + ''' + Create a tricubic hermite element field for a wedge element, where xi2 collapsed on xi3 = 0 or Xi3 = 1. + :return: Element field template + ''' + eft = self.createEftBasic() + setEftScaleFactorIds(eft, [1], []) + + valid = True + if collapseNodes in [[1, 2], [3, 4]]: + nodes = [1, 2, 3, 4] + # remap parameters on xi3 = 0 before collapsing nodes + if collapseNodes == [1, 2]: + remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS2, [1])]) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS2, []) + elif collapseNodes == [3, 4]: + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS2, []) + remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS2, [])]) + else: + valid = False + ln_map = [1, 2, 1, 2, 3, 4, 5, 6] + elif collapseNodes in [[5, 6], [7, 8]]: + nodes = [5, 6, 7, 8] + # remap parameters on xi3 = 1 before collapsing nodes + if collapseNodes == [5, 6]: + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS2, []) + remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS2, [])]) + elif collapseNodes == [7, 8]: + remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS2, [1])]) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS2, []) + else: + valid = False + ln_map = [1, 2, 3, 4, 5, 6, 5, 6] + else: + valid = False + + if not valid: + assert False, "createEftWedgeCollapseXi2Quadrant. Not implemented for collapse nodes " + str(collapseNodes) + + # zero cross derivative parameters + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D2_DS1DS2, []) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D2_DS1DS3, []) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D2_DS2DS3, []) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D3_DS1DS2DS3, []) + + remapEftLocalNodes(eft, 6, ln_map) + assert eft.validate(), 'eftfactory_tricubichermite.createEftWedgeCollapseXi2Quadrant: Failed to validate eft' + return eft + + def createEftTetrahedronCollapseXi1Xi2Quadrant(self, peakNode, sideNode): + ''' + Create a tricubic hermite element field for a tetrahedron element, where xi1 and xi2 are collapsed on xi3 + , and then two nodes are collapsed on the other end of xi3. + :param peakNode: is the top node of the tetrahedron. + :param sideNode: is the node at the other end of xi3 which has another node collapsed onto it. + :return: Element field template + ''' + eft = self.createEftBasic() + setEftScaleFactorIds(eft, [1], []) + + nodes = [5, 6, 7, 8] + # remap parameters on xi3 = 1 before collapsing nodes + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, []) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS2, []) + + valid = True + if peakNode == 8: + remapEftNodeValueLabel(eft, [5, 6], Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS2, [])]) + remapEftNodeValueLabel(eft, [7], Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS1, [])]) + if sideNode == 2: + # remap parameters on xi2 = 0 before collapsing nodes + remapEftNodeValueLabel(eft, [1, 2], Node.VALUE_LABEL_D_DS1, []) + remapEftNodeValueLabel(eft, [1], Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [1])]) + else: + valid = False + ln_map = [1, 1, 2, 3, 4, 4, 4, 4] + elif peakNode == 7: + remapEftNodeValueLabel(eft, [5, 6], Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS2, [])]) + remapEftNodeValueLabel(eft, [8], Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS1, [1])]) + if sideNode == 1: + # remap parameters on xi2 = 0 before collapsing nodes + remapEftNodeValueLabel(eft, [1, 2], Node.VALUE_LABEL_D_DS1, []) + remapEftNodeValueLabel(eft, [2], Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [])]) + else: + valid = False + ln_map = [1, 1, 2, 3, 4, 4, 4, 4] + elif peakNode == 6: + remapEftNodeValueLabel(eft, [7, 8], Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS2, [1])]) + remapEftNodeValueLabel(eft, [5], Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS1, [])]) + if sideNode == 3: + # remap parameters on xi2 = 1 before collapsing nodes + remapEftNodeValueLabel(eft, [3, 4], Node.VALUE_LABEL_D_DS1, []) + remapEftNodeValueLabel(eft, [3], Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [])]) + else: + valid = False + ln_map = [1, 2, 3, 3, 4, 4, 4, 4] + elif peakNode == 5: + remapEftNodeValueLabel(eft, [7, 8], Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS2, [1])]) + remapEftNodeValueLabel(eft, [6], Node.VALUE_LABEL_D_DS3, [(Node.VALUE_LABEL_D_DS1, [1])]) + if sideNode == 3: + # remap parameters on xi2 = 1 before collapsing nodes + remapEftNodeValueLabel(eft, [3, 4], Node.VALUE_LABEL_D_DS1, []) + remapEftNodeValueLabel(eft, [4], Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [1])]) + else: + valid = False + ln_map = [1, 2, 3, 3, 4, 4, 4, 4] + else: + valid = False + + if not valid: + assert False, "createEftTetrahedronCollapseXi1Xi2Quadrant. Not implemented for peak node " + str(peakNode) + " and side node " + str(sideNode) + + # zero cross derivative parameters + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D2_DS1DS2, []) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D2_DS1DS3, []) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D2_DS2DS3, []) + remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D3_DS1DS2DS3, []) + + remapEftLocalNodes(eft, 4, ln_map) + assert eft.validate(), 'eftfactory_tricubichermite.createEftTetrahedronCollapseXi1Xi2Quadrant: Failed to validate eft' + return eft + def createEftSplitXi1LeftStraight(self): ''' Create an element field template suitable for the inner elements of the