Package: pygalmesh
Create high-quality meshes with ease.pygalmesh is a Python frontend toCGAL's2Dand3D mesh generation
capabilities. pygalmesh makes it easy
to create high-quality 2D, 3D volume meshes, periodic volume meshes, and surface meshes.Examples2D meshesCGAL generates 2D meshes from linear constraints.importnumpyasnpimportpygalmeshpoints=np.array([[0.0,0.0],[1.0,0.0],[1.0,1.0],[0.0,1.0]])constraints=[[0,1],[1,2],[2,3],[3,0]]mesh=pygalmesh.generate_2d(points,constraints,max_edge_size=1.0e-1,num_lloyd_steps=10,)# mesh.points, mesh.cellsThe quality of the mesh isn't very good, but can be improved withoptimesh.A simple ballimportpygalmeshs=pygalmesh.Ball([0,0,0],1.0)mesh=pygalmesh.generate_mesh(s,max_cell_circumradius=0.2)# mesh.points, mesh.cells, ...You can write the mesh withmesh.write("out.vtk")You can use any format supported bymeshio.The mesh generation comes with many more options, describedhere. Try, for example,mesh=pygalmesh.generate_mesh(s,max_cell_circumradius=0.2,odt=True,lloyd=True,verbose=False)Other primitive shapespygalmesh provides out-of-the-box support for balls, cuboids, ellipsoids, tori, cones,
cylinders, and tetrahedra. Try for exampleimportpygalmeshs0=pygalmesh.Tetrahedron([0.0,0.0,0.0],[1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0])mesh=pygalmesh.generate_mesh(s0,max_cell_circumradius=0.1,max_edge_size_at_feature_edges=0.1,)Domain combinationsSupported are unions, intersections, and differences of all domains. As mentioned above,
however, the sharp intersections between two domains are not automatically handled. Try
for exampleimportpygalmeshradius=1.0displacement=0.5s0=pygalmesh.Ball([displacement,0,0],radius)s1=pygalmesh.Ball([-displacement,0,0],radius)u=pygalmesh.Difference(s0,s1)To sharpen the intersection circle, add it as a feature edge polygon line, e.g.,importnumpyasnpimportpygalmeshradius=1.0displacement=0.5s0=pygalmesh.Ball([displacement,0,0],radius)s1=pygalmesh.Ball([-displacement,0,0],radius)u=pygalmesh.Difference(s0,s1)# add circlea=np.sqrt(radius**2-displacement**2)max_edge_size_at_feature_edges=0.15n=int(2*np.pi*a/max_edge_size_at_feature_edges)alpha=np.linspace(0.0,2*np.pi,n+1)alpha[-1]=alpha[0]circ=a*np.column_stack([np.zeros(n+1),np.cos(alpha),np.sin(alpha)])mesh=pygalmesh.generate_mesh(u,extra_feature_edges=[circ],max_cell_circumradius=0.15,max_edge_size_at_feature_edges=max_edge_size_at_feature_edges,min_facet_angle=25,max_radius_surface_delaunay_ball=0.15,max_circumradius_edge_ratio=2.0,)Note that the length of the polygon legs are kept in sync withmax_edge_size_at_feature_edgesof the mesh generation. This makes sure that it fits in
nicely with the rest of the mesh.Domain deformationsYou can of course translate, rotate, scale, and stretch any domain. Try, for example,importpygalmeshs=pygalmesh.Stretch(pygalmesh.Ball([0,0,0],1.0),[1.0,2.0,0.0])mesh=pygalmesh.generate_mesh(s,max_cell_circumradius=0.1)Extrusion of 2D polygonspygalmesh lets you extrude any polygon into a 3D body. It even supports rotation
alongside!importpygalmeshp=pygalmesh.Polygon2D([[-0.5,-0.3],[0.5,-0.3],[0.0,0.5]])max_edge_size_at_feature_edges=0.1domain=pygalmesh.Extrude(p,[0.0,0.0,1.0],0.5*3.14159265359,max_edge_size_at_feature_edges,)mesh=pygalmesh.generate_mesh(domain,max_cell_circumradius=0.1,max_edge_size_at_feature_edges=max_edge_size_at_feature_edges,verbose=False,)Feature edges are automatically preserved here, which is why an edge length needs to be
given topygalmesh.Extrude.Rotation bodiesPolygons in the x-z-plane can also be rotated around the z-axis to yield a rotation
body.importpygalmeshp=pygalmesh.Polygon2D([[0.5,-0.3],[1.5,-0.3],[1.0,0.5]])max_edge_size_at_feature_edges=0.1domain=pygalmesh.RingExtrude(p,max_edge_size_at_feature_edges)mesh=pygalmesh.generate_mesh(domain,max_cell_circumradius=0.1,max_edge_size_at_feature_edges=max_edge_size_at_feature_edges,verbose=False,)Your own custom level set functionIf all of the variety is not enough for you, you can define your own custom level set
function. You simply need to subclasspygalmesh.DomainBaseand specify a function,
e.g.,importpygalmeshclassHeart(pygalmesh.DomainBase):def__init__(self):super().__init__()defeval(self,x):return((x[0]**2+9.0/4.0*x[1]**2+x[2]**2-1)**3-x[0]**2*x[2]**3-9.0/80.0*x[1]**2*x[2]**3)defget_bounding_sphere_squared_radius(self):return10.0d=Heart()mesh=pygalmesh.generate_mesh(d,max_cell_circumradius=0.1)Note that you need to specify the square of a bounding sphere radius, used as an input
to CGAL's mesh generator.Local refinementUsegenerate_meshwith a function (regular or lambda) asmax_cell_circumradius. The
same goes formax_edge_size_at_feature_edges,max_radius_surface_delaunay_ball, andmax_facet_distance.importnumpyasnpimportpygalmeshmesh=pygalmesh.generate_mesh(pygalmesh.Ball([0.0,0.0,0.0],1.0),min_facet_angle=30.0,max_radius_surface_delaunay_ball=0.1,max_facet_distance=0.025,max_circumradius_edge_ratio=2.0,max_cell_circumradius=lambdax:abs(np.sqrt(np.dot(x,x))-0.5)/5+0.025,)Surface meshesIf you're only after the surface of a body, pygalmesh hasgenerate_surface_meshfor
you. It offers fewer options (obviously,max_cell_circumradiusis gone), but otherwise
works the same way:importpygalmeshs=pygalmesh.Ball([0,0,0],1.0)mesh=pygalmesh.generate_surface_mesh(s,min_facet_angle=30.0,max_radius_surface_delaunay_ball=0.1,max_facet_distance=0.1,)Refer toCGAL's
documentationfor the
options.Periodic volume meshespygalmesh also interfaces CGAL's3D periodic
mesh generation. Besides a
domain, one needs to specify a bounding box, and optionally the number of copies in the
output (1, 2, 4, or 8). Example:importnumpyasnpimportpygalmeshclassSchwarz(pygalmesh.DomainBase):def__init__(self):super().__init__()defeval(self,x):x2=np.cos(x[0]*2*np.pi)y2=np.cos(x[1]*2*np.pi)z2=np.cos(x[2]*2*np.pi)returnx2+y2+z2mesh=pygalmesh.generate_periodic_mesh(Schwarz(),[0,0,0,1,1,1],max_cell_circumradius=0.05,min_facet_angle=30,max_radius_surface_delaunay_ball=0.05,max_facet_distance=0.025,max_circumradius_edge_ratio=2.0,number_of_copies_in_output=4,# odt=True,# lloyd=True,verbose=False,)Volume meshes from surface meshesIf you have a surface mesh at hand (likeelephant.vtu), pygalmesh generates a
volume mesh on the command line viapygalmesh volume-from-surface elephant.vtu out.vtk --cell-size 1.0 --odt(Seepygalmesh volume-from-surface -hfor all options.)In Python, doimportpygalmeshmesh=pygalmesh.generate_volume_mesh_from_surface_mesh("elephant.vtu",min_facet_angle=25.0,max_radius_surface_delaunay_ball=0.15,max_facet_distance=0.008,max_circumradius_edge_ratio=3.0,verbose=False,)Meshes from INR voxel filesIt is also possible to generate meshes from INR voxel files, e.g.,hereeither on
the command linepygalmesh from-inr skull_2.9.inr out.vtu --cell-size 5.0 --odt(seepygalmesh from-inr -hfor all options) or from Pythonimportpygalmeshmesh=pygalmesh.generate_from_inr("skull_2.9.inr",max_cell_circumradius=5.0,verbose=False,)Meshes from numpy arrays representing 3D imagespygalmesh can help generating unstructed meshes from 3D numpy int arrays specifying the
subdomains. Subdomains with key0are not meshed.importpygalmeshimportnumpyasnpx_=np.linspace(-1.0,1.0,50)y_=np.linspace(-1.0,1.0,50)z_=np.linspace(-1.0,1.0,50)x,y,z=np.meshgrid(x_,y_,z_)vol=np.empty((50,50,50),dtype=np.uint8)idx=x**2+y**2+z**2<0.5**2vol[idx]=1vol[~idx]=0voxel_size=(0.1,0.1,0.1)mesh=pygalmesh.generate_from_array(vol,voxel_size,max_facet_distance=0.2,max_cell_circumradius=0.1)mesh.write("ball.vtk")The code below creates a mesh from the 3D breast phantom fromLou et
alavailablehere.
The phantom comprises four tissue types (background, fat, fibrograndular, skin, vascular
tissues). The generated mesh conforms to tissues interfaces.importpygalmeshimportmeshiowithopen("MergedPhantom.DAT","rb")asfid:vol=np.fromfile(fid,dtype=np.uint8)vol=vol.reshape((722,411,284))voxel_size=(0.2,0.2,0.2)mesh=pygalmesh.generate_from_array(vol,voxel_size,max_facet_distance=0.2,max_cell_circumradius=1.0)mesh.write("breast.vtk")In addition, we can specify different mesh sizes for each tissue type. The code below
sets the mesh size to1 mmfor the skin tissue (label4),0.5 mmfor the vascular
tissue (label5), and2 mmfor all other tissues (default).mesh=pygalmesh.generate_from_array(vol,voxel_size,max_facet_distance=0.2,max_cell_circumradius={"default":2.0,4:1.0,5:0.5},)mesh.write("breast_adapted.vtk")Surface remeshingpygalmesh can help remeshing an existing surface mesh, e.g.,lion-head.off. On
the command line, usepygalmesh remesh-surface lion-head.off out.vtu -e 0.025 -a 25 -s 0.1 -d 0.001(seepygalmesh remesh-surface -hfor all options) or from Pythonimportpygalmeshmesh=pygalmesh.remesh_surface("lion-head.off",max_edge_size_at_feature_edges=0.025,min_facet_angle=25,max_radius_surface_delaunay_ball=0.1,max_facet_distance=0.001,verbose=False,)InstallationFor installation, pygalmesh needsCGALandEigeninstalled on your
system. They are typically available on your Linux distribution, e.g., on
Ubuntusudo apt install libcgal-dev libeigen3-devOn MacOS with homebrew,brew install cgal eigenAfter that, pygalmesh can beinstalled from the Python Package
Index, so withpip install -U pygalmeshyou can install/upgrade.TroubleshootingIf pygalmesh fails to build due tofatal error: 'Eigen/Dense' file not foundyou will need to create a symbolic link for Eigen to be detected, e.g.cd /usr/local/include
sudo ln -sf eigen3/Eigen EigenIt's possible thateigen3could be in/usr/includeinstead of/usr/local/install.Manual installationFor manual installation (if you're a developer or just really keen on getting
the bleeding edge version of pygalmesh), there are two possibilities:Get the sources, typepython3 setup.py install. This does the trick
most the time.As a fallback, there's a CMake-based installation. Simply gocmake /path/to/sources/andmake.TestingTo run the pygalmesh unit tests, check out this repository and typepytestBackgroundCGAL offers two different approaches for mesh generation:Meshes defined implicitly by level sets of functions.Meshes defined by a set of bounding planes.pygalmesh provides a front-end to the first approach, which has the following advantages
and disadvantages:All boundary points are guaranteed to be in the level set within any specified
residual. This results in smooth curved surfaces.Sharp intersections of subdomains (e.g., in unions or differences of sets) need to be
specified manually (via feature edges, see below), which can be tedious.On the other hand, the bounding-plane approach (realized bymshr), has the following properties:Smooth, curved domains are approximated by a set of bounding planes, resulting in more
of less visible edges.Intersections of domains can be computed automatically, so domain unions etc. have
sharp edges where they belong.Seeherefor other
mesh generation tools.LicenseThis software is available under theGPLv3
licenseas well as a commercial
license. If you'd like to use this software commercially, please contact the
author.