Pygalmesh
:spider_web: A Python interface to CGAL's meshing tools
Install / Use
/learn @meshpro/PygalmeshREADME
pygalmesh is a Python frontend to CGAL's 2D and 3D mesh generation capabilities. pygalmesh makes it easy to create high-quality 2D, 3D volume meshes, periodic volume meshes, and surface meshes.
Examples
2D meshes
<img src="https://meshpro.github.io/pygalmesh/rect.svg" width="30%">CGAL generates 2D meshes from linear constraints.
import numpy as np
import pygalmesh
points = 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.cells
The quality of the mesh isn't very good, but can be improved with optimesh.
A simple ball
<img src="https://meshpro.github.io/pygalmesh/ball.png" width="30%">import pygalmesh
s = 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 with
<!--pytest-codeblocks:skip-->mesh.write("out.vtk")
You can use any format supported by meshio.
The mesh generation comes with many more options, described here. Try, for example,
<!--pytest-codeblocks:skip-->mesh = pygalmesh.generate_mesh(
s, max_cell_circumradius=0.2, odt=True, lloyd=True, verbose=False
)
Other primitive shapes
<img src="https://meshpro.github.io/pygalmesh/tetra.png" width="30%">pygalmesh provides out-of-the-box support for balls, cuboids, ellipsoids, tori, cones, cylinders, and tetrahedra. Try for example
import pygalmesh
s0 = 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 combinations
<img src="https://meshpro.github.io/pygalmesh/ball-difference.png" width="30%">Supported are unions, intersections, and differences of all domains. As mentioned above, however, the sharp intersections between two domains are not automatically handled. Try for example
import pygalmesh
radius = 1.0
displacement = 0.5
s0 = 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.,
import numpy as np
import pygalmesh
radius = 1.0
displacement = 0.5
s0 = pygalmesh.Ball([displacement, 0, 0], radius)
s1 = pygalmesh.Ball([-displacement, 0, 0], radius)
u = pygalmesh.Difference(s0, s1)
# add circle
a = np.sqrt(radius**2 - displacement**2)
max_edge_size_at_feature_edges = 0.15
n = 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 with
max_edge_size_at_feature_edges of the mesh generation. This makes sure that it fits in
nicely with the rest of the mesh.
Domain deformations
<img src="https://meshpro.github.io/pygalmesh/egg.png" width="30%">You can of course translate, rotate, scale, and stretch any domain. Try, for example,
import pygalmesh
s = 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 polygons
<img src="https://meshpro.github.io/pygalmesh/triangle-rotated.png" width="30%">pygalmesh lets you extrude any polygon into a 3D body. It even supports rotation alongside!
import pygalmesh
p = pygalmesh.Polygon2D([[-0.5, -0.3], [0.5, -0.3], [0.0, 0.5]])
max_edge_size_at_feature_edges = 0.1
domain = 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 to pygalmesh.Extrude.
Rotation bodies
<img src="https://meshpro.github.io/pygalmesh/circle-rotate-extr.png" width="30%">Polygons in the x-z-plane can also be rotated around the z-axis to yield a rotation body.
import pygalmesh
p = pygalmesh.Polygon2D([[0.5, -0.3], [1.5, -0.3], [1.0, 0.5]])
max_edge_size_at_feature_edges = 0.1
domain = 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 function
<img src="https://meshpro.github.io/pygalmesh/heart.png" width="30%">If all of the variety is not enough for you, you can define your own custom level set
function. You simply need to subclass pygalmesh.DomainBase and specify a function,
e.g.,
import pygalmesh
class Heart(pygalmesh.DomainBase):
def __init__(self):
super().__init__()
def eval(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
)
def get_bounding_sphere_squared_radius(self):
return 10.0
d = 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 refinement
<img src="https://meshpro.github.io/pygalmesh/ball-local-refinement.png" width="30%">Use generate_mesh with a function (regular or lambda) as max_cell_circumradius. The
same goes for max_edge_size_at_feature_edges, max_radius_surface_delaunay_ball, and
max_facet_distance.
import numpy as np
import pygalmesh
mesh = 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=lambda x: abs(np.sqrt(np.dot(x, x)) - 0.5) / 5 + 0.025,
)
Surface meshes
If you're only after the surface of a body, pygalmesh has generate_surface_mesh for
you. It offers fewer options (obviously, max_cell_circumradius is gone), but otherwise
works the same way:
import pygalmesh
s = 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 to CGAL's documentation for the options.
Periodic volume meshes
<img src="https://meshpro.github.io/pygalmesh/periodic.png" width="30%">pygalmesh also interfaces CGAL's 3D 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:
import numpy as np
import pygalmesh
class Schwarz(pygalmesh.DomainBase):
def __init__(self):
super().__init__()
def eval(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)
return x2 + y2
