#
##
## SPDX-FileCopyrightText: © 2007-2023 Benedict Verhegghe <bverheg@gmail.com>
## SPDX-License-Identifier: GPL-3.0-or-later
##
## This file is part of pyFormex 3.4 (Thu Nov 16 18:07:39 CET 2023)
## pyFormex is a tool for generating, manipulating and transforming 3D
## geometrical models by sequences of mathematical operations.
## Home page: https://pyformex.org
## Project page: https://savannah.nongnu.org/projects/pyformex/
## Development: https://gitlab.com/bverheg/pyformex
## 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/.
##
"""Gambit neutral file exporter.
This module contains some functions to export
pyFormex mesh models to a Gambit neutral file.
"""
from time import strftime, gmtime
import numpy as np
import pyformex as pf
from pyformex import utils
from pyformex import arraytools as at
from pyformex.path import Path
# Gambit starts counting at 1 for elements and nodes
# this defines the offset for nodes (nofs), elements (eofs) and faces (fofs)
nofs, eofs, fofs = 1, 1, 1
# Element type translation from pyFormex to Gambit neutral
pyf_neu_eltype = {
'line2': (1, 2),
'line3': (1, 3),
'quad4': (2, 4),
'quad8': (2, 8),
'quad9': (2, 9),
'tri3': (3, 3),
'tri6': (3, 6),
# 'tri7': (3, 7), # NA
'hex8': (4, 8),
'hex20': (4, 20),
'hex27': (4, 27),
'wedge6': (5, 6),
# 'wedge15': (5, 15), # NA
# 'wedge18': (5, 18), # NA
'tet4': (6, 4),
'tet10': (6, 10),
# 'pyram5': (7, 5), # NA
# 'pyram13': (7, 13), # NA
# 'pyram14': (7, 14), # NA
# 'pyram18': (7, 18), # NA
# 'pyram19': (7, 19), # NA
}
# This is the order of the gambit nodes in pyFormex !!!!
neu_pyf_order = {
'quad8': (0, 2, 4, 6, 1, 3, 5, 7),
'quad9': (0, 2, 4, 6, 1, 3, 5, 7, 8),
'tri6': (0, 2, 4, 1, 3, 5),
'hex8': (0, 1, 3, 2, 4, 5, 7, 6),
'hex20': (0,2,7,5, 12,14,19,17, 1,4,6,3, 13,16,18,15, 8,9,11,10),
'tet10': (0, 2, 5, 9, 1, 3, 6, 4, 8, 7),
}
# This is the order of the gambit faces in pyFormex !!!!
pyf_neu_faces = {
'hex8': (3, 1, 0, 2, 4, 5),
'hex20': (3, 1, 0, 2, 4, 5),
'hex27': (3, 1, 0, 2, 4, 5),
'wedge6': 'TODO',
'tet4': (3, 1, 0, 2),
'tet10': (3, 1, 0, 2),
}
[docs]def writeHeading(fil, ncoords, nelems, ngroups, nbsets, heading):
"""Write the heading of the Gambit neutral file."""
datetime = strftime('%d %b %Y %H:%M:%S', gmtime())
version = pf.__version__.split('-')[0]
fil.write(f"""\
CONTROL INFO 2.4.6
** GAMBIT NEUTRAL FILE
{heading[:80]}
PROGRAM: GAMBIT VERSION: 2.4.6
{datetime}
NUMNP NELEM NGRPS NBSETS NDFCD NDFVL
""")
fil.write('%10i%10i%10i%10i%10i%10i\n' % (
ncoords, nelems, ngroups, nbsets, 3, 3))
fil.write('ENDOFSECTION\n')
[docs]def writeNodes(fil, coords):
"""Write the nodal coordinates to a Gambit neutral file"""
pf.verbose(2, f"Writing {len(coords)} nodes")
fil.write(' NODAL COORDINATES 2.4.6\n')
for i, n in enumerate(coords):
fil.write("%10d%20.11e%20.11e%20.11e\n" % ((i+nofs,)+tuple(n)))
fil.write('ENDOFSECTION\n')
[docs]def writeElems(fil, elems, eltyp, nplex):
"""Write the element connectivity to a Gambit neutral file"""
pf.verbose(2, f"Writing {len(elems)} elements of type {eltyp} mplex {nplex}")
fmt = '%8d %2d %2d ' + '%8d' * min(7,nplex) + '\n'
more = nplex-7
while more > 0:
fmt += ' '*15 + '%8d' * min(7, more) + '\n'
more -= 7
fil.write(' ELEMENTS/CELLS 2.4.6\n')
for i, e in enumerate(elems+nofs):
fil.write(fmt%((i+eofs, eltyp, nplex)+tuple(e)))
fil.write('ENDOFSECTION\n')
[docs]def writeGroups(fil, groups):
"""Write element groups to a Gambit neutral file"""
pf.verbose(2, f"Writing {len(groups)} element groups")
for i, g in enumerate(groups):
els = groups[g]
nels = len(els)
name = f'prop-{g}'
flags = (0,)
nflags = len(flags)
fil.write(' ELEMENT GROUP 2.4.6\n')
fil.write('GROUP:%11d ELEMENTS:%11d MATERIAL:%11d NFLAGS:%11d\n' % (
i+1, nels, g, nflags))
fil.write('%32s\n' % name)
fil.write(''.join((f'{flag:>8d}' for flag in flags)) + '\n')
for i in range(0, nels, 10):
fil.write(''.join((f'{n:>8d}' for n in els[i:i+10])) + '\n')
fil.write('ENDOFSECTION\n')
[docs]def writeBCsets(fil, bcsets, eltyp, order):
"""Write boundary conditions to a Gambit neutral file"""
pf.verbose(2, f"Writing {len(bcsets)} BC sets")
for bc in bcsets:
fil.write('BOUNDARY CONDITIONS 2.4.6\n')
faces = bcsets[bc]
if order is not None:
faces[:, 1] = order[faces[:, 1]]
faces += (eofs, fofs) # Gambit starts counting from 1
itype = 1 # currently only face BC
nentry = len(faces)
nvalues = 0
ibcodes = (6, ) # currently ELEMENT_SIDE only
fmt = '%32s' + '%8d' * (3+len(ibcodes)) + '\n'
fil.write(fmt % ((bc, itype, nentry, nvalues) + ibcodes))
for v in faces:
fil.write('%10d%5d%5d\n'%(v[0], eltyp, v[1]))
fil.write('ENDOFSECTION\n')
[docs]def writeNEU(filename, M, bcsets={}, heading=None):
"""Export a Mesh in Gambit neutral format
Parameters
----------
filename: :term:`path_like`
The output file name, commonly having a suffix '.neu'.
If the suffix ends on '.gz' or '.bz2', the file will transparently
be compressed during writing.
M: :class:`Mesh`
The Mesh to be be written to the file.
If the Mesh has prop values, an element group will be added to the
file for each of the unique values in M.prop. The prop value will
be written as the material type number.
If M has no prop values, a single group of all elements is written
with material type number 0.
heading: str
A title line to be shown in the .neu file header.
bcsets: dict
A dictionary of boundary conditions where the keys are names and
the values are arrays with two columns: column one are the element
numbers and column two are the local face numbers. See Notes
See also
-----
https://web.stanford.edu/class/me469b/handouts/gambit_write.pdf
Notes
-----
bcsets is currently limited to writing ELEMENT/SIDE boundary conditions
for the faces of volume elements of eltype 'tet4', 'hex8' or 'hex20'.
The borderface arrays for use in bcsets can be obtained from the second
return value in::
M.getFreeEntities(level=-1,return_indices=True)
or from matching a surface Mesh::
M.matchFaces(S)[1]
Examples
--------
>>> from pyformex.mesh import Mesh
>>> f = Path('test_filewrite.neu')
>>> M = Mesh(eltype='quad4')
>>> writeNEU(f, M)
>>> print(f.read_text())
CONTROL INFO 2.4.6
** GAMBIT NEUTRAL FILE
Generated by pyFormex ...
PROGRAM: GAMBIT VERSION: 2.4.6
...
NUMNP NELEM NGRPS NBSETS NDFCD NDFVL
4 1 1 0 3 3
ENDOFSECTION
NODAL COORDINATES 2.4.6
1 0.00000000000e+00 0.00000000000e+00 0.00000000000e+00
2 1.00000000000e+00 0.00000000000e+00 0.00000000000e+00
3 1.00000000000e+00 1.00000000000e+00 0.00000000000e+00
4 0.00000000000e+00 1.00000000000e+00 0.00000000000e+00
ENDOFSECTION
ELEMENTS/CELLS 2.4.6
1 2 4 1 2 3 4
ENDOFSECTION
ELEMENT GROUP 2.4.6
GROUP: 1 ELEMENTS: 1 MATERIAL: 0 NFLAGS: 1
prop-0
0
0
ENDOFSECTION
>>> f.remove()
"""
filename = Path(filename)
pf.verbose(1, f"Write NEU file {filename.absolute()}")
if heading is None:
heading = f"Generated by {pf.fullVersion()}"
elname = M.elName()
try:
eltyp, nplex = pyf_neu_eltype[elname]
except KeyError:
raise ValueError(
f"A Mesh with '{M.elName()}' can not be exported to .neu format,"
f" supported eltypes are {list(pyf_neu_eltype)}")
ncoords = M.ncoords()
nelems = M.nelems()
if nplex != M.nplex():
raise pf.ImplementationError("Non-matching plexitude!")
if elname in neu_pyf_order:
order = at.inverseUniqueIndex(neu_pyf_order[elname])
elems = M.elems[:, order]
else:
elems = M.elems
if M.prop is None:
groups = { 0: np.arange(nelems) }
else:
groups = M.propDict()
with utils.File(filename, 'w') as fil:
writeHeading(fil, ncoords, nelems, len(groups), len(bcsets), heading)
writeNodes(fil, M.coords)
writeElems(fil, elems, eltyp, nplex)
writeGroups(fil, groups)
if bcsets:
try:
order = np.array(pyf_neu_faces[elname], dtype=at.Int)
except KeyError:
order = None
writeBCsets(fil, bcsets, eltyp, order)
pf.verbose(2, f"Mesh exported to {filename}")
pf.verbose(2, f"File size: {filename.size} bytes")
# End