fileread — pyFormex 1.0.5 documentation

# Source code for fileread

    #
    ##
    ##  This file is part of pyFormex 1.0.5  (Sat Feb 16 10:40:32 CET 2019)
    ##  pyFormex is a tool for generating, manipulating and transforming 3D
    ##  geometrical models by sequences of mathematical operations.
    ##  Home page: http://pyformex.org
    ##  Project page:  http://savannah.nongnu.org/projects/pyformex/
    ##  Copyright 2004-2018 (C) Benedict Verhegghe (benedict.verhegghe@ugent.be)
    ##  Distributed under the GNU General Public License version 3 or later.
    ##
    ##  This program is free software: you can redistribute it and/or modify
    ##  it under the terms of the GNU General Public License as published by
    ##  the Free Software Foundation, either version 3 of the License, or
    ##  (at your option) any later version.
    ##
    ##  This program is distributed in the hope that it will be useful,
    ##  but WITHOUT ANY WARRANTY; without even the implied warranty of
    ##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ##  GNU General Public License for more details.
    ##
    ##  You should have received a copy of the GNU General Public License
    ##  along with this program.  If not, see http://www.gnu.org/licenses/.
    ##
    """Read geometry from file in a whole number of formats.

    This module defines basic routines to read geometrical data
    from a file and the specialized importers to read files in a number of
    well known standardized formats.

    The basic routines are very versatile as well as optimized (using the version
    in the pyFormex C-library) and allow to easily create new exporters for
    other formats.
    """
    from __future__ import absolute_import, division, print_function
    from pyformex import zip

    import pyformex as pf
    import pyformex.arraytools as at
    from pyformex.connectivity import Connectivity
    from pyformex.coords import Coords
    from pyformex.mesh import Mesh
    from pyformex import utils
    import os
    import numpy as np

    # Global variables used by some funnctions
    filename = None
    mode = None
    nplex = None
    offset = None

    def getParams(line):
        """Strip the parameters from a comment line"""
        s = line.split()
        d = {'mode': s.pop(0),'filename': s.pop(0)}
        d.update(zip(s[::2], s[1::2]))
        return d

    def readNodes(fil):
        """Read a set of nodes from an open mesh file"""
        a = np.fromfile(fil, sep=" ").reshape(-1, 3)
        x = Coords(a)
        return x

    def readElems(fil, nplex):
        """Read a set of elems of plexitude nplex from an open mesh file"""
        print("Reading elements of plexitude %s" % nplex)
        e = np.fromfile(fil, sep=" ", dtype=np.Int).reshape(-1, nplex)
        e = Connectivity(e)
        return e

    def readEsets(fil):
        """Read the eset data of type generate"""
        data = []
        for line in fil:
            s = line.strip('\n').split()
            if len(s) == 4:
                data.append(s[:1]+ [int(i) for i in s[1:]])
        return data

    def readMeshFile(fn):
        """Read a nodes/elems model from file.

        Returns a dict:

        - `coords`: a Coords with all nodes
        - `elems`: a list of Connectivities
        - `esets`: a list of element sets
        """
        d = {}
        fil = open(fn, 'r')
        for line in fil:
            if line[0] == '#':
                line = line[1:]
            globals().update(getParams(line))
            dfil = open(filename, 'r')
            if mode == 'nodes':
                d['coords'] = readNodes(dfil)
            elif mode == 'elems':
                elems = d.setdefault('elems', [])
                e = readElems(dfil, int(nplex)) - int(offset)
                elems.append(e)
            elif mode == 'esets':
                d['esets'] = readEsets(dfil)
            else:
                print("Skipping unrecognized line: %s" % line)
            dfil.close()

        fil.close()
        return d

    def extractMeshes(d):
        """Extract the Meshes read from a .mesh file.

        """
        x = d['coords']
        e = d['elems']
        M = [ Mesh(x, ei) for ei in e ]
        return M

    def convertInp(fn):
        """Convert an Abaqus .inp to a .mesh set of files"""
        converter = os.path.join(pf.cfg['pyformexdir'], 'bin', 'read_abq_inp.awk')
        fn = os.path.abspath(fn)
        dirname = os.path.dirname(fn)
        basename = os.path.basename(fn)
        cmd = 'cd %s;%s %s' % (dirname, converter, basename)
        utils.command(cmd, shell=True)

    def readInpFile(filename):
        """Read the geometry from an Abaqus/Calculix .inp file

        This is a replacement for the convertInp/readMeshFile combination.
        It uses the ccxinp plugin to provide a direct import of the Finite
        Element meshes from an Abaqus or Calculix input file.
        Currently still experimental and limited in functionality (aimed
        primarily at Calculix). But also many simple meshes from Abaqus can
        already be read.

        Returns an dict.
        """
        from pyformex.plugins import ccxinp, fe
        ccxinp.skip_unknown_eltype = True
        model = ccxinp.readInput(filename)
        print("Number of parts: %s" % len(model.parts))
        fem = {}
        for part in model.parts:
            try:
                coords = Coords(part['coords'])
                nodid = part['nodid']
                nodpos = at.inverseUniqueIndex(nodid)
                print("nnodes = %s" % coords.shape[0])
                print("nodid: %s" % nodid)
                print("nodpos: %s" % nodpos)
                for e in part['elems']:
                    print("Orig els %s" % e[1])
                    print("Trl els %s" % nodpos[e[1]])
                elems = [ Connectivity(nodpos[e], eltype=t) for (t, e) in part['elems'] ]
                print('ELEM TYPES: %s' % [e.eltype for e in elems])
                fem[part['name']] = fe.Model(coords, elems)
            except:
                print("Skipping part %s" % part['name'])
        return fem

    def read_off(fn):
        """Read an OFF surface mesh.

        The mesh should consist of only triangles!
        Returns a nodes,elems tuple.
        """
        print("Reading .OFF %s" % fn)
        with utils.File(fn, 'r') as fil:
            head = fil.readline().strip()
            if head != "OFF":
                print("%s is not an OFF file!" % fn)
                return None, None
            nnodes, nelems, nedges = [int(i) for i in fil.readline().split()]
            nodes = np.fromfile(file=fil, dtype=at.Float, count=3*nnodes, sep=' ')
            # elems have number of vertices + 3 vertex numbers
            elems = np.fromfile(file=fil, dtype=np.int32, count=4*nelems, sep=' ')
        print("Read %d nodes and %d elems" % (nnodes, nelems))
        return nodes.reshape((-1, 3)), elems.reshape((-1, 4))[:, 1:]

    def read_gts(fn):
        """Read a GTS surface mesh.

        Return a coords,edges,faces tuple.
        """
        print("Reading GTS file %s" % fn)
        with utils.File(fn, 'r') as fil:
            header = fil.readline().split()
            ncoords, nedges, nfaces = [int(i) for i in header[:3]]
            if len(header) >= 7 and header[6].endswith('Binary'):
                raise RuntimeError("We can not read binary GTS format yet. See https://savannah.nongnu.org/bugs/index.php?38608. Maybe you should recompile the extra/gts commands.")
                sep=''
            else:
                sep=' '
            coords = np.fromfile(fil, dtype=at.Float, count=3*ncoords, sep=sep).reshape(-1, 3)
            edges = np.fromfile(fil, dtype=np.int32, count=2*nedges, sep=' ').reshape(-1, 2) - 1
            faces = np.fromfile(fil, dtype=np.int32, count=3*nfaces, sep=' ').reshape(-1, 3) - 1
        print("Read %d coords, %d edges, %d faces" % (ncoords, nedges, nfaces))
        if coords.shape[0] != ncoords or \
           edges.shape[0] != nedges or \
           faces.shape[0] != nfaces:
            print("Error while reading GTS file: the file is probably incorrect!")
        return coords, edges, faces

    def read_stl_bin(fn):
        """Read a binary stl.

        Parameters
        ----------
        fn: str
            Name of the file to read, holding binary STL data.

        Returns
        -------
        Coords (ntri,4,3)
            A Coords with ntri triangles. Each triangle consists of 4
            items: the first one is the normal, the other three are the
            coordinates of the vertices.

        """
        def addTriangle(i):
            x[i] = np.fromfile(file=fil, dtype=at.Float, count=12).reshape(4, 3)
            fil.read(2)

        print("Reading binary .STL %s" % fn)
        fil = open(fn, 'rb')
        head = fil.read(80)
        if head[:5] == 'solid':
            raise ValueError("%s looks like an ASCII STL file!" % fn)
        i = head.find('COLOR=')
        if i >= 0 and i <= 70:
            print("HAS COLOR")
            color = np.fromstring(head[i+6:i+10], dtype=np.uint8, count=4)
        else:
            color = None

        ntri = np.fromfile(file=fil, dtype=at.Int, count=1)[0]
        print("Number of triangles: %s" % ntri)
        x = np.zeros((ntri, 4, 3), dtype=at.Float)
        #nbytes = 12*4 + 2
        [ addTriangle(it) for it in range(ntri) ]
        print("Finished reading binary stl")
        x = Coords(x)
        if color is not None:
            from pyformex.opengl.colors import GLcolor
            color = GLcolor(color[:3])
        return x, color

    def read_gambit_neutral(fn):
        """Read a triangular surface mesh in Gambit neutral format.

        The .neu file nodes are numbered from 1!
        Returns a nodes,elems tuple.
        """
        scr = os.path.join(pf.cfg['bindir'], 'gambit-neu ')
        utils.command("%s '%s'" % (scr, fn))
        nodesf = utils.changeExt(fn, '.nodes')
        elemsf = utils.changeExt(fn, '.elems')
        nodes = np.fromfile(nodesf, sep=' ', dtype=at.Float).reshape((-1, 3))
        elems = np.fromfile(elemsf, sep=' ', dtype=np.int32).reshape((-1, 3))
        return nodes, elems-1

    def read_gambit_neutral_hex(fn):
        """Read an hexahedral mesh in Gambit neutral format.

        The .neu file nodes are numbered from 1!
        Returns a nodes,elems tuple.
        """
        scr = os.path.join(pf.cfg['bindir'], 'gambit-neu-hex ')
        print("%s '%s'" % (scr, fn))
        utils.command("%s '%s'" % (scr, fn))
        nodesf = utils.changeExt(fn, '.nodes')
        elemsf = utils.changeExt(fn, '.elems')
        nodes = np.fromfile(nodesf, sep=' ', dtype=at.Float).reshape((-1, 3))
        elems = np.fromfile(elemsf, sep=' ', dtype=np.int32).reshape((-1, 8))
        elems = elems[:, (0, 1, 3, 2, 4, 5, 7, 6)]
        return nodes, elems-1

    # End