SkillAgentSearch skills...

Pygalmesh

:spider_web: A Python interface to CGAL's meshing tools

Install / Use

/learn @meshpro/Pygalmesh
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<p align="center"> <a href="https://github.com/nschloe/pygalmesh"><img alt="pygalmesh" src="https://meshpro.github.io/pygalmesh/pygalmesh-logo.svg" width="60%"></a> <p align="center">Create high-quality meshes with ease.</p> </p>

PyPi Version Anaconda Cloud Packaging status PyPI pyversions DOI GitHub stars Downloads

<!--[![PyPi downloads](https://img.shields.io/pypi/dm/pygalmesh.svg?style=flat-square)](https://pypistats.org/packages/pygalmesh)-->

Discord

gh-actions codecov LGTM Code style: black

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
View on GitHub
GitHub Stars665
CategoryDevelopment
Updated1d ago
Forks66

Languages

C++

Security Score

100/100

Audited on Mar 30, 2026

No findings