#
##
## This file is part of pyFormex 2.0 (Mon Sep 14 12:29:05 CEST 2020)
## 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-2020 (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/.
##
"""Gambit neutral file exporter.
This module contains some functions to export
pyFormex mesh models to Gambit neutral files.
"""
from time import strftime, gmtime
from numpy import *
from pyformex import Path
from pyformex.plugins import tetgen
# 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
[docs]def writeHeading(fil, nodes, elems, nbsets=0, heading=''):
"""Write the heading of the Gambit neutral file.
`nbsets`: number of boundary condition sets (border patches).
"""
fil.write(" CONTROL INFO 2.4.6\n")
fil.write("** GAMBIT NEUTRAL FILE\n")
fil.write('%s\n' %heading)
fil.write('PROGRAM: Gambit VERSION: 2.4.6\n')
fil.write(strftime('%d %b %Y %H:%M:%S\n', gmtime()))
fil.write(' NUMNP NELEM NGRPS NBSETS NDFCD NDFVL\n')
fil.write('%10i%10i%10i%10i%10i%10i\n' % (shape(nodes)[0], shape(elems)[0], 1, nbsets, 3, 3))
fil.write('ENDOFSECTION\n')
[docs]def writeNodes(fil, nodes):
"""Write nodal coordinates.
"""
fil.write(' NODAL COORDINATES 2.4.6\n')
for i, n in enumerate(nodes):
fil.write("%10d%20.11e%20.11e%20.11e\n" % ((i+nofs,)+tuple(n)))
fil.write('ENDOFSECTION\n')
[docs]def writeElems(fil, elems):
"""Write element connectivity.
"""
shape = elems.shape[1]
# Library to define the gambit element type number
gamb_el_library = { # 'line2':1, #Edge
'quad4': 2, #Quadrilateral
'tri3': 3, #Triangle
'hex8': 4, #Brick
'wedge6': 5, # Wedge (Prism)
'tet4': 6, #Tetrahedron
#'pyr5':7, #Pyramid
}
try:
gamb_shape = gamb_el_library[elems.eltype.name()]
except KeyError:
raise TypeError("Mesh element type '%s' not convertable"%(elems.eltype.name()))
#Gambit uses a different convention for the numbering of hex-8 elements
if gamb_shape==4: # hex-8
elems = elems[:, (0, 1, 3, 2, 4, 5, 7, 6)]
fmt = '%8d %2d %2d %8d%8d%8d%8d%8d%8d%8d\n %8d\n'
elif gamb_shape==6: # tet-4
elems = elems[:, (0, 2, 3, 1)]
fmt = '%8d %2d %2d %8d%8d%8d%8d\n'
fil.write(' ELEMENTS/CELLS 2.4.6\n')
for i, e in enumerate(elems+nofs):
fil.write(fmt%((i+eofs, gamb_shape, shape)+tuple(e)))
fil.write('ENDOFSECTION\n')
[docs]def writeGroup(fil, elems):
"""Write group of elements.
"""
fil.write(' ELEMENT GROUP 2.4.6\n')
fil.write('GROUP:%11d ELEMENTS:%11d MATERIAL:%11d NFLAGS:%11d\n' % (1, shape(elems)[0], 2, 1))
fil.write('%32s\n' %'fluid')
fil.write('%8d\n' %0)
n = shape(elems)[0]//10
for i in range(n):
fil.write('%8d%8d%8d%8d%8d%8d%8d%8d%8d%8d\n' %(10*i+1, 10*i+2, 10*i+3, 10*i+4, 10*i+5, 10*i+6, 10*i+7, 10*i+8, 10*i+9, 10*i+10))
for j in range(shape(elems)[0]-10*n):
fil.write('%8d' %(10*n+j+1))
fil.write('\n')
fil.write('ENDOFSECTION\n')
[docs]def writeBCsets(fil, bcsets, elgeotype):
"""Write boundary condition sets of faces.
Parameters:
- `bcsets`: a dict where the values are BorderFace arrays (see below).
- `elgeotype`: element geometry type: 4 for hexahedrons, 6 for
tetrahedrons.
BorderFace array: A set of border faces defined as a (n,2) shaped int
array: echo row contains an element number (enr) and face number (fnr).
There are 2 ways to construct the BorderFace arrays:
# find border both as mesh and enr/fnr and keep correspondence::
brde, brdfaces = M.getFreeEntities(level=-1,return_indices=True)
brd = Mesh(M.coords, brde)
.. note: This needs further explanation. Gianluca?
# matchFaces: Given a volume mesh M and a surface meshes S, being
(part of) the border of M, BorderFace array for the surface S can
be obtained from::
bf = M.matchFaces(S)[1]
To define other boundary types:
Value - Boundary Entity Type,
0 UNSPECIFIED, 1 AXIS, 2 CONJUGATE, 3 CONVECTION, 4 CYCLIC,
5 DEAD, 6 ELEMENT_SIDE, 7 ESPECIES, 8 EXHAUST_FAN, 9 FAN,
10 FREE_SURFACE, 11 GAP, 12 INFLOW, 13 INLET, 14 INLET_VENT,
15 INTAKE_FAN, 16 INTERFACE, 17 INTERIOR, 18 INTERNAL, 19 LIVE,
20 MASS_FLOW_INLET, 21 MELT, 22 MELT_INTERFACE,
23 MOVING_BOUNDARY, 24 NODE, 25 OUTFLOW, 26 OUTLET,
27 OUTLET_VENT, 28 PERIODIC, 29 PLOT, 30 POROUS, 31 POROUS_JUMP,
32 PRESSURE, 33 PRESSURE_FAR_FIELD, 34 PRESSURE_INFLOW,
35 PRESSURE_INLET, 36 PRESSURE_OUTFLOW, 37 PRESSURE_OUTLET,
38 RADIATION, 39 RADIATOR , 40 RECIRCULATION_INLET, 41 RECIRCULATION_OUTLET,
42 SLIP, 43 SREACTION, 44 SURFACE, 45 SYMMETRY, 46 TRACTION, 47 TRAJECTORY,
48 VELOCITY, 49 VELOCITY_INLET, 50 VENT, 51 WALL, 52 SPRING
See also
http://combust.hit.edu.cn:8080/fluent/Gambit13_help/modeling_guide/mg0b.htm#mg0b01
for the description of the neu file syntax.
"""
if elgeotype==4:
py2neuHF = asarray([3, 1, 0, 2, 4, 5]) # hex faces numbering conversion (pyformex to gambit neu)
elif elgeotype==6:
py2neuHF = asarray([1, 3, 2, 0]) # tet faces numbering conversion (pyformex to gambit neu)
if bcsets is not None:
for k in bcsets:
print ('Writing BC set : %s\n'%k)
fil.write('BOUNDARY CONDITIONS 2.4.6\n')
val = bcsets[k]
val[:, 1]=py2neuHF[val[:, 1]]
val+=fofs # faces are counted starting from 1
fil.write('%32s 1%8d 0%8d\n'%(k, len(val), 6)) # patchname, 0/1 is node/face,nr of faces, 0,
#type of BC (6 = ELEMENT_SIDE : OK if you import the neu file in fluent)
for v in val:
txt='%10d%5d%5d\n'%(v[0], elgeotype, v[1]) # elem nr, el type (4 is hex, 6 is tet), face nr
fil.write(txt)
fil.write('ENDOFSECTION\n')
[docs]def write_neu(fil, mesh, bcsets=None, heading='generated with pyFormex'):
"""Export a mesh as .neu file (For use in Gambit/Fluent)
- `fil`: file name
- `mesh`: pyFormex Mesh
- `heading`: heading text to be shown in the gambit header
- `bcsets`: dictionary of 2D arrays: {'name1': brdfaces1, ...}, see writeBCsets
"""
if mesh.elName()=='hex8':
elgeotype=4
elif mesh.elName()=='tet4':
elgeotype=6
else:
raise ValueError("Can only write 'hex8' or 'tet4' Mesh to .neu file")
if not fil.endswith('.neu'):
fil += '.neu'
f = open(fil, 'w')
if bcsets is None:
nbsets =0
else:
nbsets=len(bcsets)
print ('Writing %d BC sets'%nbsets)
writeHeading(f, mesh.coords, mesh.elems, nbsets=nbsets, heading=heading)
print('Writing %s nodes to .neu file'% len(mesh.coords))
writeNodes(f, mesh.coords)
print("Writing %s elements of type '%s' to .neu file"%(len(mesh.elems), mesh.elems.eltype.name()))
writeElems(f, mesh.elems)
writeGroup(f, mesh.elems)
writeBCsets(f, bcsets, elgeotype)
f.close()
print("Mesh exported to '%s'"%fil)
def testConverts():
"""_This debug function generates a .neu file for each element type"""
chdir('~')
testdir = Path.cwd() / 'f2fluTests'
testdir.mkdir(exist_ok=True)
chdir(testdir)
from pyformex.simple import cuboid
mesh = Formex([0., 0., 0.]).extrude(3, dir=0).toMesh().extrude(3, dir=1)
write_neu('test-quad4.neu', mesh)
write_neu('test-tri3.neu', mesh.convert('tri3'))
mesh = cuboid().toMesh().convert('hex8-8')
write_neu('test-hex8.neu', mesh)
mesh = Mesh([[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], [0., 0., 1.], [1., 0., 1.], [0., 1., 1.]], [[0, 1, 2, 3, 4, 5]])
write_neu('test-wed6.neu', mesh)
mesh = Mesh([[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], [0., 0., 1.]], [[0, 1, 2, 3]], eltype='tet4')
write_neu('test-tet4.neu', mesh)
# This is special for pyFormex scripts !
if __name__ == '__draw__':
testConverts()
# End