211 lines
7.1 KiB
Python
211 lines
7.1 KiB
Python
|
import tempfile
|
||
|
|
||
|
import numpy as np
|
||
|
|
||
|
|
||
|
def load_gmsh(file_name, gmsh_args=None):
|
||
|
"""
|
||
|
Returns a surface mesh from CAD model in Open Cascade
|
||
|
Breap (.brep), Step (.stp or .step) and Iges formats
|
||
|
Or returns a surface mesh from 3D volume mesh using gmsh.
|
||
|
|
||
|
For a list of possible options to pass to GMSH, check:
|
||
|
http://gmsh.info/doc/texinfo/gmsh.html
|
||
|
|
||
|
An easy way to install the GMSH SDK is through the `gmsh-sdk`
|
||
|
package on PyPi, which downloads and sets up gmsh:
|
||
|
>>> pip install gmsh-sdk
|
||
|
|
||
|
Parameters
|
||
|
--------------
|
||
|
file_name : str
|
||
|
Location of the file to be imported
|
||
|
gmsh_args : (n, 2) list
|
||
|
List of (parameter, value) pairs to be passed to
|
||
|
gmsh.option.setNumber
|
||
|
max_element : float or None
|
||
|
Maximum length of an element in the volume mesh
|
||
|
|
||
|
Returns
|
||
|
------------
|
||
|
mesh : trimesh.Trimesh
|
||
|
Surface mesh of input geometry
|
||
|
"""
|
||
|
# use STL as an intermediate format
|
||
|
from ..exchange.stl import load_stl
|
||
|
# do import here to avoid very occasional segfaults
|
||
|
import gmsh
|
||
|
|
||
|
# start with default args for the meshing step
|
||
|
# Mesh.Algorithm=2 MeshAdapt/Delaunay, there are others but they may include quads
|
||
|
# With this planes are meshed using Delaunay and cylinders are meshed
|
||
|
# using MeshAdapt
|
||
|
args = [("Mesh.Algorithm", 2),
|
||
|
("Mesh.CharacteristicLengthFromCurvature", 1),
|
||
|
("Mesh.MinimumCirclePoints", 32)]
|
||
|
# add passed argument tuples last so we can override defaults
|
||
|
if gmsh_args is not None:
|
||
|
args.extend(gmsh_args)
|
||
|
|
||
|
# formats GMSH can load
|
||
|
supported = ['.brep', '.stp', '.step', '.igs', '.iges',
|
||
|
'.bdf', '.msh', '.inp', '.diff', '.mesh']
|
||
|
|
||
|
# check extensions to make sure it is supported format
|
||
|
if file_name is not None:
|
||
|
if not any(file_name.lower().endswith(e)
|
||
|
for e in supported):
|
||
|
raise ValueError(
|
||
|
'Supported formats are: BREP (.brep), STEP (.stp or .step), ' +
|
||
|
'IGES (.igs or .iges), Nastran (.bdf), Gmsh (.msh), Abaqus (*.inp), ' +
|
||
|
'Diffpack (*.diff), Inria Medit (*.mesh)')
|
||
|
else:
|
||
|
raise ValueError('No import since no file was provided!')
|
||
|
|
||
|
# if we initialize with sys.argv it could be anything
|
||
|
gmsh.initialize()
|
||
|
gmsh.option.setNumber("General.Terminal", 1)
|
||
|
gmsh.model.add('Surface_Mesh_Generation')
|
||
|
gmsh.open(file_name)
|
||
|
|
||
|
# create a temporary file for the results
|
||
|
out_data = tempfile.NamedTemporaryFile(suffix='.stl', delete=False)
|
||
|
# windows gets mad if two processes try to open the same file
|
||
|
out_data.close()
|
||
|
|
||
|
# we have to mesh the surface as these are analytic BREP formats
|
||
|
if any(file_name.lower().endswith(e)
|
||
|
for e in ['.brep', '.stp', '.step', '.igs', '.iges']):
|
||
|
gmsh.model.geo.synchronize()
|
||
|
# loop through our numbered args which do things, stuff
|
||
|
for arg in args:
|
||
|
gmsh.option.setNumber(*arg)
|
||
|
# generate the mesh
|
||
|
gmsh.model.mesh.generate(2)
|
||
|
# write to the temporary file
|
||
|
gmsh.write(out_data.name)
|
||
|
else:
|
||
|
gmsh.plugin.run("NewView")
|
||
|
gmsh.plugin.run("Skin")
|
||
|
gmsh.view.write(1, out_data.name)
|
||
|
|
||
|
# load the data from the temporary outfile
|
||
|
with open(out_data.name, 'rb') as f:
|
||
|
kwargs = load_stl(f)
|
||
|
|
||
|
return kwargs
|
||
|
|
||
|
|
||
|
def to_volume(mesh,
|
||
|
file_name=None,
|
||
|
max_element=None,
|
||
|
mesher_id=1):
|
||
|
"""
|
||
|
Convert a surface mesh to a 3D volume mesh generated by gmsh.
|
||
|
|
||
|
An easy way to install the gmsh sdk is through the gmsh-sdk
|
||
|
package on pypi, which downloads and sets up gmsh:
|
||
|
pip install gmsh-sdk
|
||
|
|
||
|
Algorithm details, although check gmsh docs for more information:
|
||
|
The "Delaunay" algorithm is split into three separate steps.
|
||
|
First, an initial mesh of the union of all the volumes in the model is performed,
|
||
|
without inserting points in the volume. The surface mesh is then recovered using H.
|
||
|
Si's boundary recovery algorithm Tetgen/BR. Then a three-dimensional version of the
|
||
|
2D Delaunay algorithm described above is applied to insert points in the volume to
|
||
|
respect the mesh size constraints.
|
||
|
|
||
|
The Frontal" algorithm uses J. Schoeberl's Netgen algorithm.
|
||
|
The "HXT" algorithm is a new efficient and parallel reimplementaton
|
||
|
of the Delaunay algorithm.
|
||
|
The "MMG3D" algorithm (experimental) allows to generate
|
||
|
anisotropic tetrahedralizations
|
||
|
|
||
|
|
||
|
Parameters
|
||
|
--------------
|
||
|
mesh : trimesh.Trimesh
|
||
|
Surface mesh of input geometry
|
||
|
file_name : str or None
|
||
|
Location to save output, in .msh (gmsh) or .bdf (Nastran) format
|
||
|
max_element : float or None
|
||
|
Maximum length of an element in the volume mesh
|
||
|
mesher_id : int
|
||
|
3D unstructured algorithms:
|
||
|
1: Delaunay, 4: Frontal, 7: MMG3D, 10: HXT
|
||
|
|
||
|
Returns
|
||
|
------------
|
||
|
data : None or bytes
|
||
|
MSH data, only returned if file_name is None
|
||
|
|
||
|
"""
|
||
|
# do import here to avoid very occasional segfaults
|
||
|
import gmsh
|
||
|
|
||
|
# checks mesher selection
|
||
|
if mesher_id not in [1, 4, 7, 10]:
|
||
|
raise ValueError('unavilable mesher selected!')
|
||
|
else:
|
||
|
mesher_id = int(mesher_id)
|
||
|
|
||
|
# set max element length to a best guess if not specified
|
||
|
if max_element is None:
|
||
|
max_element = np.sqrt(np.mean(mesh.area_faces))
|
||
|
|
||
|
if file_name is not None:
|
||
|
# check extensions to make sure it is supported format
|
||
|
if not any(file_name.lower().endswith(e)
|
||
|
for e in ['.bdf', '.msh', '.inp', '.diff', '.mesh']):
|
||
|
raise ValueError(
|
||
|
'Only Nastran (.bdf), Gmsh (.msh), Abaqus (*.inp), ' +
|
||
|
'Diffpack (*.diff) and Inria Medit (*.mesh) formats ' +
|
||
|
'are available!')
|
||
|
|
||
|
# exports to disk for gmsh to read using a temp file
|
||
|
mesh_file = tempfile.NamedTemporaryFile(suffix='.stl', delete=False)
|
||
|
mesh_file.close()
|
||
|
mesh.export(mesh_file.name)
|
||
|
|
||
|
# starts Gmsh Python API script
|
||
|
gmsh.initialize()
|
||
|
gmsh.option.setNumber("General.Terminal", 1)
|
||
|
gmsh.model.add('Nastran_stl')
|
||
|
|
||
|
gmsh.merge(mesh_file.name)
|
||
|
dimtag = gmsh.model.getEntities()[0]
|
||
|
dim = dimtag[0]
|
||
|
tag = dimtag[1]
|
||
|
|
||
|
surf_loop = gmsh.model.geo.addSurfaceLoop([tag])
|
||
|
gmsh.model.geo.addVolume([surf_loop])
|
||
|
gmsh.model.geo.synchronize()
|
||
|
|
||
|
# We can then generate a 3D mesh...
|
||
|
gmsh.option.setNumber("Mesh.Algorithm3D", mesher_id)
|
||
|
gmsh.option.setNumber("Mesh.CharacteristicLengthMax", max_element)
|
||
|
gmsh.model.mesh.generate(3)
|
||
|
|
||
|
dimtag2 = gmsh.model.getEntities()[1]
|
||
|
dim2 = dimtag2[0]
|
||
|
tag2 = dimtag2[1]
|
||
|
p2 = gmsh.model.addPhysicalGroup(dim2, [tag2])
|
||
|
gmsh.model.setPhysicalName(dim, p2, 'Nastran_bdf')
|
||
|
|
||
|
data = None
|
||
|
# if file name is None, return msh data using a tempfile
|
||
|
if file_name is None:
|
||
|
out_data = tempfile.NamedTemporaryFile(suffix='.msh', delete=False)
|
||
|
# windows gets mad if two processes try to open the same file
|
||
|
out_data.close()
|
||
|
gmsh.write(out_data.name)
|
||
|
with open(out_data.name, 'rb') as f:
|
||
|
data = f.read()
|
||
|
else:
|
||
|
gmsh.write(file_name)
|
||
|
|
||
|
# close up shop
|
||
|
gmsh.finalize()
|
||
|
|
||
|
return data
|