Example #1
0
def trimesh2d(vertices,
              segments=None,
              holes=None,
              maxArea=None,
              quality=True,
              dofs_per_node=1,
              logFilename="tri.log",
              triangleExecutablePath=None):
    """
    Triangulates an area described by a number vertices (vertices) and a set
    of segments that describes a closed polygon. 
    
    Parameters:
    
        vertices            array [nVertices x 2] with vertices describing the geometry.
        
                            [[v0_x, v0_y],
                             [   ...    ],
                             [vn_x, vn_y]]
                             
        segments            array [nSegments x 3] with segments describing the geometry.
                            
                            [[s0_v0, s0_v1,marker],
                             [        ...        ],
                             [sn_v0, sn_v1,marker]]
                             
        holes               [Not currently used]
        
        maxArea             Maximum area for triangle. (None)
        
        quality             If true, triangles are prevented having angles < 30 degrees. (True)
        
        dofs_per_node         Number of degrees of freedom per node.
        
        logFilename         Filename for triangle output ("tri.log")
        
    Returns:
    
        coords              Node coordinates
        
                            [[n0_x, n0_y],
                             [   ...    ],
                             [nn_x, nn_y]]
                             
        edof                Element topology
        
                            [[el0_dof1, ..., el0_dofn],
                             [          ...          ],
                             [eln_dof1, ..., eln_dofn]]
                             
        dofs                Node dofs
        
                            [[n0_dof1, ..., n0_dofn],
                             [         ...         ],
                             [nn_dof1, ..., nn_dofn]]
                             
        bdofs               Boundary dofs. Dictionary containing lists of dofs for
                            each boundary marker. Dictionary key = marker id.
        
    """

    # Check for triangle executable

    triangleExecutable = triangleExecutablePath

    if triangleExecutable == None:
        triangleExecutable = ""
        if sys.platform == "win32":
            triangleExecutable = which("triangle.exe")
        else:
            triangleExecutable = which("triangle")
    else:
        if not os.path.exists(triangleExecutable):
            triangleExecutable = None

    if triangleExecutable == None:
        print(
            "Error: Could not find triangle. Please make sure that the \ntriangle executable is available on the search path (PATH)."
        )
        return None, None, None, None

    # Create triangle options

    options = ""

    if maxArea != None:
        options += "-a%f " % maxArea + " "
    if quality:
        options += "-q"

    # Set initial variables

    nSegments = 0
    nHoles = 0
    nAttribs = 0
    nBoundaryMarkers = 1
    nVertices = len(vertices)

    # All files are created as temporary files

    if not os.path.exists("./trimesh.temp"):
        os.mkdir("./trimesh.temp")

    filename = "./trimesh.temp/polyfile.poly"

    if not segments is None:
        nSegments = len(segments)

    if not holes is None:
        nHoles = len(holes)

    # Create a .poly file

    polyFile = open(filename, "w")
    polyFile.write("%d 2 %d \n" % (nVertices, nAttribs))

    i = 0

    for vertex in vertices:
        polyFile.write("%d %g %g\n" % (i, vertex[0], vertex[1]))
        i = i + 1

    polyFile.write("%d %d \n" % (nSegments, nBoundaryMarkers))

    i = 0

    for segment in segments:
        polyFile.write("%d %d %d %d\n" %
                       (i, segment[0], segment[1], segment[2]))
        i = i + 1

    polyFile.write("0\n")

    polyFile.close()

    # Execute triangle

    os.system("%s %s %s > tri.log" % (triangleExecutable, options, filename))

    # Read results from triangle

    strippedName = os.path.splitext(filename)[0]

    nodeFilename = "%s.1.node" % strippedName
    elementFilename = "%s.1.ele" % strippedName
    polyFilename = "%s.1.poly" % strippedName

    # Read vertices

    allVertices = None
    boundaryVertices = {}

    if os.path.exists(nodeFilename):
        nodeFile = open(nodeFilename, "r")
        nodeInfo = list(map(int, nodeFile.readline().split()))

        nNodes = nodeInfo[0]

        allVertices = np.zeros([nNodes, 2], 'd')

        for i in range(nNodes):
            vertexRow = list(map(float, nodeFile.readline().split()))

            boundaryMarker = int(vertexRow[3])

            if not (boundaryMarker in boundaryVertices):
                boundaryVertices[boundaryMarker] = []

            allVertices[i, :] = [vertexRow[1], vertexRow[2]]
            boundaryVertices[boundaryMarker].append(i + 1)

        nodeFile.close()

    # Read elements

    elements = []

    if os.path.exists(elementFilename):
        elementFile = open(elementFilename, "r")
        elementInfo = list(map(int, elementFile.readline().split()))

        nElements = elementInfo[0]

        elements = np.zeros([nElements, 3], 'i')

        for i in range(nElements):
            elementRow = list(map(int, elementFile.readline().split()))
            elements[i, :] = [
                elementRow[1] + 1, elementRow[2] + 1, elementRow[3] + 1
            ]

        elementFile.close()

    # Clean up

    try:
        pass
        #os.remove(filename)
        #os.remove(nodeFilename)
        #os.remove(elementFilename)
        #os.remove(polyFilename)
    except:
        pass

    # Add dofs in edof and bcVerts

    dofs = cfc.createdofs(np.size(allVertices, 0), dofs_per_node)

    if dofs_per_node > 1:
        expandedElements = np.zeros((np.size(elements, 0), 3 * dofs_per_node),
                                    'i')
        dofs = cfc.createdofs(np.size(allVertices, 0), dofs_per_node)

        elIdx = 0

        for elementTopo in elements:
            for i in range(3):
                expandedElements[elIdx, i * dofs_per_node:(
                    i * dofs_per_node + dofs_per_node)] = dofs[elementTopo[i] -
                                                               1, :]
            elIdx += 1

        for bVertIdx in boundaryVertices.keys():
            bVert = boundaryVertices[bVertIdx]
            bVertNew = []
            for i in range(len(bVert)):
                for j in range(dofs_per_node):
                    bVertNew.append(dofs[bVert[i] - 1][j])

            boundaryVertices[bVertIdx] = bVertNew

        return allVertices, np.asarray(
            expandedElements), dofs, boundaryVertices

    return allVertices, elements, dofs, boundaryVertices
Example #2
0
def trimesh2d(
    vertices,
    segments=None,
    holes=None,
    maxArea=None,
    quality=True,
    dofsPerNode=1,
    logFilename="tri.log",
    triangleExecutablePath=None,
):
    """
    Triangulates an area described by a number vertices (vertices) and a set
    of segments that describes a closed polygon. 
    
    Parameters:
    
        vertices            array [nVertices x 2] with vertices describing the geometry.
        
                            [[v0_x, v0_y],
                             [   ...    ],
                             [vn_x, vn_y]]
                             
        segments            array [nSegments x 3] with segments describing the geometry.
                            
                            [[s0_v0, s0_v1,marker],
                             [        ...        ],
                             [sn_v0, sn_v1,marker]]
                             
        holes               [Not currently used]
        
        maxArea             Maximum area for triangle. (None)
        
        quality             If true, triangles are prevented having angles < 30 degrees. (True)
        
        dofsPerNode         Number of degrees of freedom per node.
        
        logFilename         Filename for triangle output ("tri.log")
        
    Returns:
    
        coords              Node coordinates
        
                            [[n0_x, n0_y],
                             [   ...    ],
                             [nn_x, nn_y]]
                             
        edof                Element topology
        
                            [[el0_dof1, ..., el0_dofn],
                             [          ...          ],
                             [eln_dof1, ..., eln_dofn]]
                             
        dofs                Node dofs
        
                            [[n0_dof1, ..., n0_dofn],
                             [         ...         ],
                             [nn_dof1, ..., nn_dofn]]
                             
        bdofs               Boundary dofs. Dictionary containing lists of dofs for
                            each boundary marker. Dictionary key = marker id.
        
    """

    # Check for triangle executable

    triangleExecutable = triangleExecutablePath

    if triangleExecutable == None:
        triangleExecutable = ""
        if sys.platform == "win32":
            triangleExecutable = which("triangle.exe")
        else:
            triangleExecutable = which("triangle")
    else:
        if not os.path.exists(triangleExecutable):
            triangleExecutable = None

    if triangleExecutable == None:
        print(
            "Error: Could not find triangle. Please make sure that the \ntriangle executable is available on the search path (PATH)."
        )
        return None, None, None, None

    # Create triangle options

    options = ""

    if maxArea != None:
        options += "-a%f " % maxArea + " "
    if quality:
        options += "-q"

    # Set initial variables

    nSegments = 0
    nHoles = 0
    nAttribs = 0
    nBoundaryMarkers = 1
    nVertices = len(vertices)

    # All files are created as temporary files

    if not os.path.exists("./trimesh.temp"):
        os.mkdir("./trimesh.temp")

    filename = "./trimesh.temp/polyfile.poly"

    if not segments is None:
        nSegments = len(segments)

    if not holes is None:
        nHoles = len(holes)

    # Create a .poly file

    polyFile = open(filename, "w")
    polyFile.write("%d 2 %d \n" % (nVertices, nAttribs))

    i = 0

    for vertex in vertices:
        polyFile.write("%d %g %g\n" % (i, vertex[0], vertex[1]))
        i = i + 1

    polyFile.write("%d %d \n" % (nSegments, nBoundaryMarkers))

    i = 0

    for segment in segments:
        polyFile.write("%d %d %d %d\n" % (i, segment[0], segment[1], segment[2]))
        i = i + 1

    polyFile.write("0\n")

    polyFile.close()

    # Execute triangle

    os.system("%s %s %s > tri.log" % (triangleExecutable, options, filename))

    # Read results from triangle

    strippedName = os.path.splitext(filename)[0]

    nodeFilename = "%s.1.node" % strippedName
    elementFilename = "%s.1.ele" % strippedName
    polyFilename = "%s.1.poly" % strippedName

    # Read vertices

    allVertices = None
    boundaryVertices = {}

    if os.path.exists(nodeFilename):
        nodeFile = open(nodeFilename, "r")
        nodeInfo = list(map(int, nodeFile.readline().split()))

        nNodes = nodeInfo[0]

        allVertices = np.zeros([nNodes, 2], "d")

        for i in range(nNodes):
            vertexRow = list(map(float, nodeFile.readline().split()))

            boundaryMarker = int(vertexRow[3])

            if not (boundaryMarker in boundaryVertices):
                boundaryVertices[boundaryMarker] = []

            allVertices[i, :] = [vertexRow[1], vertexRow[2]]
            boundaryVertices[boundaryMarker].append(i + 1)

        nodeFile.close()

    # Read elements

    elements = []

    if os.path.exists(elementFilename):
        elementFile = open(elementFilename, "r")
        elementInfo = list(map(int, elementFile.readline().split()))

        nElements = elementInfo[0]

        elements = np.zeros([nElements, 3], "i")

        for i in range(nElements):
            elementRow = list(map(int, elementFile.readline().split()))
            elements[i, :] = [elementRow[1] + 1, elementRow[2] + 1, elementRow[3] + 1]

        elementFile.close()

    # Clean up

    try:
        pass
        # os.remove(filename)
        # os.remove(nodeFilename)
        # os.remove(elementFilename)
        # os.remove(polyFilename)
    except:
        pass

    # Add dofs in edof and bcVerts

    dofs = cfc.createdofs(np.size(allVertices, 0), dofsPerNode)

    if dofsPerNode > 1:
        expandedElements = np.zeros((np.size(elements, 0), 3 * dofsPerNode), "i")
        dofs = cfc.createdofs(np.size(allVertices, 0), dofsPerNode)

        elIdx = 0

        for elementTopo in elements:
            for i in range(3):
                expandedElements[elIdx, i * dofsPerNode : (i * dofsPerNode + dofsPerNode)] = dofs[elementTopo[i] - 1, :]
            elIdx += 1

        for bVertIdx in boundaryVertices.keys():
            bVert = boundaryVertices[bVertIdx]
            bVertNew = []
            for i in range(len(bVert)):
                for j in range(dofsPerNode):
                    bVertNew.append(dofs[bVert[i] - 1][j])

            boundaryVertices[bVertIdx] = bVertNew

        return allVertices, np.asarray(expandedElements), dofs, boundaryVertices

    return allVertices, elements, dofs, boundaryVertices
Example #3
0
    def create(self, is3D=False):
        '''
        Meshes a surface or volume defined by the geometry in geoData.
        Parameters:
        is3D - Optional parameter that only needs to be set if geometry
               is loaded from a geo-file, i.e. if geoData is a path string.
               Default False.
        
        Returns:
        
            coords          Node coordinates
            
                            [[n0_x, n0_y, n0_z],
                            [   ...           ],
                            [nn_x, nn_y, nn_z]]
                            
            edof            Element topology
                            
                            [[el0_dof1, ..., el0_dofn],
                            [          ...          ],
                            [eln_dof1, ..., eln_dofn]]
                                 
            dofs            Node dofs
            
                            [[n0_dof1, ..., n0_dofn],
                            [         ...         ],
                            [nn_dof1, ..., nn_dofn]]
                                 
            bdofs           Boundary dofs. Dictionary containing lists of dofs for
                            each boundary marker. Dictionary key = marker id.
                            
            elementmarkers  List of integer markers. Row i contains the marker of
                            element i. Markers are similar to boundary markers and
                            can be used to identify in which region an element lies.

            boundaryElements  (optional) returned if self.return_boundary_elements is true.
                              Contains dictionary with boundary elements. The keys are markers
                              and the values are lists of elements for that marker.
                            
    Running this function also creates object variables:
            
            nodesOnCurve    Dictionary containing lists of node-indices. Key is a 
                            curve-ID and the value is a list of indices of all nodes
                            on that curve, including its end points.
            
            nodesOnSurface  Dictionary containing lists of node-indices. Key is a
                            surface-ID and the value is a list of indices of the nodes
                            on that surface, including its boundary.
            
            nodesOnVolume   Dictionary containing lists of node-indices. Key is a
                            volume-ID and the value is a list of indices of the nodes
                            in that volume, including its surface. 
        '''
        #Nodes per element for different element types:
        #(taken from Chapter 9, page 89 of the gmsh manual)
        nodesPerElmDict = {
            1: 2,
            2: 3,
            3: 4,
            4: 4,
            5: 8,
            6: 6,
            7: 5,
            8: 3,
            9: 6,
            10: 9,
            11: 10,
            12: 27,
            13: 18,
            14: 14,
            15: 1,
            16: 8,
            17: 20,
            18: 15,
            19: 13,
            20: 9,
            21: 10,
            22: 12,
            23: 15,
            24: 15,
            25: 21,
            26: 4,
            27: 5,
            28: 6,
            29: 20,
            30: 35,
            31: 56,
            92: 64,
            93: 125
        }
        nodesPerElement = nodesPerElmDict[self.el_type]

        # Check for GMSH executable [NOTE]Mostly copied from trimesh2d(). TODO: Test on different systems
        gmshExe = self.gmsh_exec_path
        if gmshExe == None:
            gmshExe = ""
            if sys.platform == "win32":
                gmshExe = which("gmsh.exe")
            else:
                gmshExe = which("gmsh")
        else:
            if not os.path.exists(gmshExe):
                gmshExe = os.path.join(os.getcwd(),
                                       self.gmsh_exec_path)  #Try relative path
                if not os.path.exists(gmshExe):
                    gmshExe = None  #Relative path didnt work either

        if gmshExe == None:
            raise IOError(
                "Error: Could not find GMSH. Please make sure that the \GMSH executable is available on the search path (PATH)."
            )

        # Create a temporary directory for GMSH

        oldStyleTempDir = False

        if self.mesh_dir != "":
            tempMeshDir = self.mesh_dir
            os.mkdir(tempMeshDir)
        else:
            tempMeshDir = tempfile.mkdtemp()

        if type(
                self.geometry
        ) is str:  #If geometry data is given as a .geo file we will just pass it on to gmsh later.
            geoFilePath = self.geometry
            dim = 3 if is3D else 2  #In this case geoData is a path string, so the dimension must be supplied by the user.
            if not os.path.exists(geoFilePath):
                geoFilePath = os.path.join(os.getcwd(),
                                           geoFilePath)  #Try relative path
                if not os.path.exists(geoFilePath):
                    raise IOError("Error: Could not find geo-file " +
                                  geoFilePath)
        else:
            dim = 3 if self.geometry.is3D else 2  #Get the dimension of the model from geoData.

            if oldStyleTempDir:
                if not os.path.exists("./gmshMeshTemp"):
                    os.mkdir("./gmshMeshTemp")
                geoFilePath = os.path.normpath(
                    os.path.join(os.getcwd(), "gmshMeshTemp/tempGeometry.geo")
                )  #"gmshMeshTemp/tempGeometry.geo"
            else:
                geoFilePath = os.path.normpath(
                    os.path.join(tempMeshDir, 'tempGeometry.geo'))

            self.geofile = open(geoFilePath, "w")  #Create temp geometry file
            self._writeGeoFile()  #Write geoData to file
            self.geofile.close()

        if oldStyleTempDir:
            mshFileName = os.path.normpath(
                os.path.join(os.getcwd(), 'gmshMeshTemp/meshFile.msh')
            )  #Filepath to the msh-file that will be generated.
        else:
            mshFileName = os.path.normpath(
                os.path.join(tempMeshDir, 'meshFile.msh'))

        #construct options string:

        options = ""
        options += ' -' + str(dim)
        options += ' -clscale ' + str(self.el_size_factor)  #scale factor
        options += ' -o \"%s\"' % mshFileName
        options += ' -clcurv' if self.clcurv else ''
        options += ' -clmin ' + str(
            self.min_size) if self.min_size is not None else ''
        options += ' -clmax ' + str(
            self.max_size) if self.max_size is not None else ''
        options += ' -algo ' + self.meshing_algorithm if self.meshing_algorithm is not None else ''
        options += ' -order 2' if self.el_type in self._2ndOrderElms else ''
        options += ' -format msh22'
        options += ' ' + self.additional_options

        #Execute gmsh

        gmshExe = os.path.normpath(gmshExe)
        print("GMSH binary: " + gmshExe)
        #print('""%s" "%s" %s"' % (gmshExe, geoFilePath, options))
        #os.system('""%s" "%s" %s"' % (gmshExe, geoFilePath, options))
        #retval = os.system(r'"%s" "%s" %s' % (gmshExe, geoFilePath, options))

        output = subprocess.Popen(r'"%s" "%s" %s' %
                                  (gmshExe, geoFilePath, options),
                                  shell=True,
                                  stdout=subprocess.PIPE).stdout.read()

        #Read generated msh file:
        #print("Opening msh file " + mshFileName)#TEMP

        mshFile = open(mshFileName, 'r')
        print("Mesh file  : " + mshFileName)

        #print("Reading msh file...")#TEMP
        ln = mshFile.readline()
        while (ln != '$Nodes\n'):  #Read until we find the nodes
            ln = mshFile.readline()
        nbrNodes = int(mshFile.readline())
        allNodes = np.zeros([nbrNodes, dim], 'd')
        for i in range(nbrNodes):
            line = list(map(float, mshFile.readline().split()))
            allNodes[i, :] = line[
                1:dim + 1]  #Grab the coordinates (1:3 if 2D, 1:4 if 3D)

        while (mshFile.readline() !=
               '$Elements\n'):  #Read until we find the elements
            pass
        nbrElements = int(mshFile.readline()
                          )  #The nbr of elements (including marker elements).
        elements = []
        elementmarkers = []
        bdofs = {
        }  #temp dictionary of sets. Key:MarkerID. Value:Set. The sets will be converted to lists.
        boundaryElements = {}
        #nodeOnPoint = {}  #dictionary pointID : nodeNumber
        self.nodesOnCurve = {}  #dictionary lineID  : set of [nodeNumber]
        self.nodesOnSurface = {}  #dictionary surfID  : set of [nodeNumber]
        self.nodesOnVolume = {}  #dictionary volID   : set of [nodeNumber]
        for i in range(
                nbrElements):  #Read all elements (points, surfaces, etc):
            line = list(map(int, mshFile.readline().split()))
            eType = line[1]  #second int is the element type.
            nbrTags = line[2]  #Third int is the nbr of tags on this element.
            marker = line[3]  #Fourth int (first tag) is the marker.
            entityID = line[
                4]  #Fifth int  is the ID of the geometric entity (points, curves, etc) that the element belongs to
            nodes = line[3 + nbrTags:len(
                line)]  #The rest after tags are node indices.

            if (
                    eType == self.el_type
            ):  #If the element type is the kind of element we are looking for:
                elements.append(
                    nodes)  #Add the nodes of the elements to the list.
                elementmarkers.append(
                    marker
                )  #Add element marker. It is used for keeping track of elements (thickness, heat-production and such)
            else:  #If the element is not a "real" element we store its node at marker in bdof instead:
                _insertInSetDict(bdofs, marker, nodes)

                # We also store the full information as 'boundary elements'
                _insertBoundaryElement(boundaryElements, eType, marker, nodes)

            #if eType == 15: #If point. Commmented away because points only make elements if they have non-zero markers, so nodeOnPoint is not very useful.
            #    nodeOnPoint[entityID-1] = nodes[0] #insert node into nodeOnPoint. (ID-1 because we want 0-based indices)

            if eType in [1, 8, 26, 27, 28]:  #If line
                _insertInSetDict(self.nodesOnCurve, entityID - 1,
                                 _offsetIndices(
                                     nodes,
                                     -1))  #insert nodes into nodesOnCurve
            elif eType in [2, 3, 9, 10, 16, 20, 21, 22, 23, 24,
                           25]:  #If surfaceelement
                _insertInSetDict(self.nodesOnSurface, entityID - 1,
                                 _offsetIndices(
                                     nodes,
                                     -1))  #insert nodes into nodesOnSurface
            else:  #if volume element.
                _insertInSetDict(self.nodesOnVolume, entityID - 1,
                                 _offsetIndices(nodes, -1))

        elements = np.array(elements)
        for key in bdofs.keys():  #Convert the sets of boundary nodes to lists.
            bdofs[key] = list(bdofs[key])
        for key in self.nodesOnCurve.keys():  #Convert set to list
            self.nodesOnCurve[key] = list(self.nodesOnCurve[key])
        for key in self.nodesOnSurface.keys():  #Convert set to list
            self.nodesOnSurface[key] = list(self.nodesOnSurface[key])
        for key in self.nodesOnVolume.keys():  #Convert set to list
            self.nodesOnVolume[key] = list(self.nodesOnVolume[key])

        mshFile.close()

        # Remove temporary mesh directory if not explicetly specified.

        if self.mesh_dir == "":
            shutil.rmtree(tempMeshDir)

        dofs = createdofs(np.size(allNodes, 0), self.dofs_per_node)

        if self.dofs_per_node > 1:  #This if-chunk copied from pycalfem_utils.py
            self.topo = elements
            expandedElements = np.zeros(
                (np.size(elements, 0), nodesPerElement * self.dofs_per_node),
                'i')
            elIdx = 0
            for elementTopo in elements:
                for i in range(nodesPerElement):
                    expandedElements[elIdx, i * self.dofs_per_node:(
                        i * self.dofs_per_node +
                        self.dofs_per_node)] = dofs[elementTopo[i] - 1, :]
                elIdx += 1

            for keyID in bdofs.keys():
                bVerts = bdofs[keyID]
                bVertsNew = []
                for i in range(len(bVerts)):
                    for j in range(self.dofs_per_node):
                        bVertsNew.append(dofs[bVerts[i] - 1][j])
                bdofs[keyID] = bVertsNew

            if self.return_boundary_elements:
                return allNodes, np.asarray(
                    expandedElements
                ), dofs, bdofs, elementmarkers, boundaryElements
            return allNodes, np.asarray(
                expandedElements), dofs, bdofs, elementmarkers

        if self.return_boundary_elements:
            return allNodes, elements, dofs, bdofs, elementmarkers, boundaryElements
        return allNodes, elements, dofs, bdofs, elementmarkers
Example #4
0
    def create(self, is3D=False):
        """
        Meshes a surface or volume defined by the geometry in geoData.
        Parameters:
        is3D - Optional parameter that only needs to be set if geometry
               is loaded from a geo-file, i.e. if geoData is a path string.
               Default False.
        
        Returns:
        
            coords          Node coordinates
            
                            [[n0_x, n0_y, n0_z],
                            [   ...           ],
                            [nn_x, nn_y, nn_z]]
                            
            edof            Element topology
                            
                            [[el0_dof1, ..., el0_dofn],
                            [          ...          ],
                            [eln_dof1, ..., eln_dofn]]
                                 
            dofs            Node dofs
            
                            [[n0_dof1, ..., n0_dofn],
                            [         ...         ],
                            [nn_dof1, ..., nn_dofn]]
                                 
            bdofs           Boundary dofs. Dictionary containing lists of dofs for
                            each boundary marker. Dictionary key = marker id.
                            
            elementmarkers  List of integer markers. Row i contains the marker of
                            element i. Markers are similar to boundary markers and
                            can be used to identify in which region an element lies.
                            
    Running this function also creates object variables:
            
            nodesOnCurve    Dictionary containing lists of node-indices. Key is a 
                            curve-ID and the value is a list of indices of all nodes
                            on that curve, including its end points.
            
            nodesOnSurface  Dictionary containing lists of node-indices. Key is a
                            surface-ID and the value is a list of indices of the nodes
                            on that surface, including its boundary.
            
            nodesOnVolume   Dictionary containing lists of node-indices. Key is a
                            volume-ID and the value is a list of indices of the nodes
                            in that volume, including its surface. 
        """
        # Nodes per element for different element types:
        # (taken from Chapter 9, page 89 of the gmsh manual)
        nodesPerElmDict = {
            1: 2,
            2: 3,
            3: 4,
            4: 4,
            5: 8,
            6: 6,
            7: 5,
            8: 3,
            9: 6,
            10: 9,
            11: 10,
            12: 27,
            13: 18,
            14: 14,
            15: 1,
            16: 8,
            17: 20,
            18: 15,
            19: 13,
            20: 9,
            21: 10,
            22: 12,
            23: 15,
            24: 15,
            25: 21,
            26: 4,
            27: 5,
            28: 6,
            29: 20,
            30: 35,
            31: 56,
            92: 64,
            93: 125,
        }
        nodesPerElement = nodesPerElmDict[self.elType]

        # Check for GMSH executable [NOTE]Mostly copied from trimesh2d(). TODO: Test on different systems
        gmshExe = self.gmshExecPath
        if gmshExe == None:
            gmshExe = ""
            if sys.platform == "win32":
                gmshExe = which("gmsh.exe")
            else:
                gmshExe = which("gmsh")
        else:
            if not os.path.exists(gmshExe):
                gmshExe = os.path.join(os.getcwd(), self.gmshExecPath)  # Try relative path
                if not os.path.exists(gmshExe):
                    gmshExe = None  # Relative path didnt work either

        if gmshExe == None:
            raise IOError(
                "Error: Could not find GMSH. Please make sure that the \GMSH executable is available on the search path (PATH)."
            )

        if (
            type(self.geometry) is str
        ):  # If geometry data is given as a .geo file we will just pass it on to gmsh later.
            geoFilePath = self.geometry
            dim = (
                3 if is3D else 2
            )  # In this case geoData is a path string, so the dimension must be supplied by the user.
            if not os.path.exists(geoFilePath):
                geoFilePath = os.path.join(os.getcwd(), geoFilePath)  # Try relative path
                if not os.path.exists(geoFilePath):
                    raise IOError("Error: Could not find geo-file " + geoFilePath)
        else:
            dim = 3 if self.geometry.is3D else 2  # Get the dimension of the model from geoData.
            if not os.path.exists("./gmshMeshTemp"):
                os.mkdir("./gmshMeshTemp")
            geoFilePath = os.path.normpath(
                os.path.join(os.getcwd(), "gmshMeshTemp/tempGeometry.geo")
            )  # "gmshMeshTemp/tempGeometry.geo"
            self.geofile = open(geoFilePath, "w")  # Create temp geometry file
            self._writeGeoFile()  # Write geoData to file
            self.geofile.close()

        mshFileName = os.path.normpath(
            os.path.join(os.getcwd(), "gmshMeshTemp/meshFile.msh")
        )  # Filepath to the msh-file that will be generated.
        # construct options string:
        options = ""
        options += " -" + str(dim)
        options += " -clscale " + str(self.elSizeFactor)  # scale factor
        options += ' -o "%s"' % mshFileName
        options += " -clcurv" if self.clcurv else ""
        options += " -clmin " + str(self.minSize) if self.minSize is not None else ""
        options += " -clmax " + str(self.maxSize) if self.maxSize is not None else ""
        options += " -algo " + self.meshingAlgorithm if self.meshingAlgorithm is not None else ""
        options += " -order 2" if self.elType in self._2ndOrderElms else ""
        options += " " + self.additionalOptions

        # Execute gmsh
        gmshExe = os.path.normpath(gmshExe)
        os.system('%s "%s" %s' % (gmshExe, geoFilePath, options))

        # Read generated msh file:
        # print("Opening msh file " + mshFileName)#TEMP

        mshFile = open(mshFileName, "r")

        # print("Reading msh file...")#TEMP
        ln = mshFile.readline()
        while ln != "$Nodes\n":  # Read until we find the nodes
            ln = mshFile.readline()
        nbrNodes = int(mshFile.readline())
        allNodes = np.zeros([nbrNodes, dim], "d")
        for i in range(nbrNodes):
            line = list(map(float, mshFile.readline().split()))
            allNodes[i, :] = line[1 : dim + 1]  # Grab the coordinates (1:3 if 2D, 1:4 if 3D)

        while mshFile.readline() != "$Elements\n":  # Read until we find the elements
            pass
        nbrElements = int(mshFile.readline())  # The nbr of elements (including marker elements).
        elements = []
        elementmarkers = []
        bdofs = {}  # temp dictionary of sets. Key:MarkerID. Value:Set. The sets will be converted to lists.
        # nodeOnPoint = {}  #dictionary pointID : nodeNumber
        self.nodesOnCurve = {}  # dictionary lineID  : set of [nodeNumber]
        self.nodesOnSurface = {}  # dictionary surfID  : set of [nodeNumber]
        self.nodesOnVolume = {}  # dictionary volID   : set of [nodeNumber]
        for i in range(nbrElements):  # Read all elements (points, surfaces, etc):
            line = list(map(int, mshFile.readline().split()))
            eType = line[1]  # second int is the element type.
            nbrTags = line[2]  # Third int is the nbr of tags on this element.
            marker = line[3]  # Fourth int (first tag) is the marker.
            entityID = line[
                4
            ]  # Fifth int  is the ID of the geometric entity (points, curves, etc) that the element belongs to
            nodes = line[3 + nbrTags : len(line)]  # The rest after tags are node indices.

            if eType == self.elType:  # If the element type is the kind of element we are looking for:
                elements.append(nodes)  # Add the nodes of the elements to the list.
                elementmarkers.append(
                    marker
                )  # Add element marker. It is used for keeping track of elements (thickness, heat-production and such)
            else:  # If the element is not a "real" element we store its node at marker in bdof instead:
                _insertInSetDict(bdofs, marker, nodes)

            # if eType == 15: #If point. Commmented away because points only make elements if they have non-zero markers, so nodeOnPoint is not very useful.
            #    nodeOnPoint[entityID-1] = nodes[0] #insert node into nodeOnPoint. (ID-1 because we want 0-based indices)
            if eType in [1, 8, 26, 27, 28]:  # If line
                _insertInSetDict(
                    self.nodesOnCurve, entityID - 1, _offsetIndices(nodes, -1)
                )  # insert nodes into nodesOnCurve
            elif eType in [2, 3, 9, 10, 16, 20, 21, 22, 23, 24, 25]:  # If surfaceelement
                _insertInSetDict(
                    self.nodesOnSurface, entityID - 1, _offsetIndices(nodes, -1)
                )  # insert nodes into nodesOnSurface
            else:  # if volume element.
                _insertInSetDict(self.nodesOnVolume, entityID - 1, _offsetIndices(nodes, -1))

        elements = np.array(elements)
        for key in bdofs.keys():  # Convert the sets of boundary nodes to lists.
            bdofs[key] = list(bdofs[key])
        for key in self.nodesOnCurve.keys():  # Convert set to list
            self.nodesOnCurve[key] = list(self.nodesOnCurve[key])
        for key in self.nodesOnSurface.keys():  # Convert set to list
            self.nodesOnSurface[key] = list(self.nodesOnSurface[key])
        for key in self.nodesOnVolume.keys():  # Convert set to list
            self.nodesOnVolume[key] = list(self.nodesOnVolume[key])

        # print("Closing msh file...")#TEMP
        mshFile.close()

        dofs = createdofs(np.size(allNodes, 0), self.dofsPerNode)

        if self.dofsPerNode > 1:  # This if-chunk copied from pycalfem_utils.py
            self.topo = elements
            expandedElements = np.zeros((np.size(elements, 0), nodesPerElement * self.dofsPerNode), "i")
            elIdx = 0
            for elementTopo in elements:
                for i in range(nodesPerElement):
                    expandedElements[elIdx, i * self.dofsPerNode : (i * self.dofsPerNode + self.dofsPerNode)] = dofs[
                        elementTopo[i] - 1, :
                    ]
                elIdx += 1

            for keyID in bdofs.keys():
                bVerts = bdofs[keyID]
                bVertsNew = []
                for i in range(len(bVerts)):
                    for j in range(self.dofsPerNode):
                        bVertsNew.append(dofs[bVerts[i] - 1][j])
                bdofs[keyID] = bVertsNew

            return allNodes, np.asarray(expandedElements), dofs, bdofs, elementmarkers

        return allNodes, elements, dofs, bdofs, elementmarkers