Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/meshpro/pygalmesh
:spider_web: A Python interface to CGAL's meshing tools
https://github.com/meshpro/pygalmesh
cgal engineering mathematics mesh mesh-generation meshing python
Last synced: 13 days ago
JSON representation
:spider_web: A Python interface to CGAL's meshing tools
- Host: GitHub
- URL: https://github.com/meshpro/pygalmesh
- Owner: meshpro
- License: gpl-3.0
- Created: 2016-08-19T15:21:39.000Z (about 8 years ago)
- Default Branch: main
- Last Pushed: 2024-07-22T09:26:24.000Z (4 months ago)
- Last Synced: 2024-10-01T16:20:49.568Z (about 1 month ago)
- Topics: cgal, engineering, mathematics, mesh, mesh-generation, meshing, python
- Language: C++
- Homepage:
- Size: 4.19 MB
- Stars: 587
- Watchers: 20
- Forks: 57
- Open Issues: 30
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Citation: CITATION.cff
Awesome Lists containing this project
- awesome-eit - pygalmesh
- awesome-scientific-computing - pygalmesh - Python interface for CGAL's 3D meshing capabilities. (Meshing / Triangular and tetrahedral meshing)
README
Create high-quality meshes with ease.
[![PyPi Version](https://img.shields.io/pypi/v/pygalmesh.svg?style=flat-square)](https://pypi.org/project/pygalmesh)
[![Anaconda Cloud](https://anaconda.org/conda-forge/pygalmesh/badges/version.svg?=style=flat-square)](https://anaconda.org/conda-forge/pygalmesh/)
[![Packaging status](https://repology.org/badge/tiny-repos/pygalmesh.svg)](https://repology.org/project/pygalmesh/versions)
[![PyPI pyversions](https://img.shields.io/pypi/pyversions/pygalmesh.svg?style=flat-square)](https://pypi.org/pypi/pygalmesh/)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.5564818.svg?style=flat-square)](https://doi.org/10.5281/zenodo.5564818)
[![GitHub stars](https://img.shields.io/github/stars/nschloe/pygalmesh.svg?style=flat-square&label=Stars&logo=github)](https://github.com/nschloe/pygalmesh)
[![Downloads](https://pepy.tech/badge/pygalmesh/month?style=flat-square)](https://pepy.tech/project/pygalmesh)[![Discord](https://img.shields.io/static/v1?logo=discord&label=chat&message=on%20discord&color=7289da&style=flat-square)](https://discord.gg/Z6DMsJh4Hr)
[![gh-actions](https://img.shields.io/github/workflow/status/nschloe/pygalmesh/ci?style=flat-square)](https://github.com/nschloe/pygalmesh/actions?query=workflow%3Aci)
[![codecov](https://img.shields.io/codecov/c/github/nschloe/pygalmesh.svg?style=flat-square)](https://codecov.io/gh/nschloe/pygalmesh)
[![LGTM](https://img.shields.io/lgtm/grade/python/github/nschloe/pygalmesh.svg?style=flat-square)](https://lgtm.com/projects/g/nschloe/pygalmesh)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/psf/black)pygalmesh is a Python frontend to [CGAL](https://www.cgal.org/)'s
[2D](https://doc.cgal.org/latest/Mesh_2/index.html) and [3D mesh generation
capabilities](https://doc.cgal.org/latest/Mesh_3/index.html). pygalmesh makes it easy
to create high-quality 2D, 3D volume meshes, periodic volume meshes, and surface meshes.### Examples
#### 2D meshes
CGAL generates 2D meshes from linear constraints.
```python
import numpy as np
import pygalmeshpoints = 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](https://github.com/nschloe/optimesh).#### A simple ball
```python
import pygalmeshs = 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
```python
mesh.write("out.vtk")
```You can use any format supported by [meshio](https://github.com/nschloe/meshio).
The mesh generation comes with many more options, described
[here](https://doc.cgal.org/latest/Mesh_3/). Try, for example,```python
mesh = pygalmesh.generate_mesh(
s, max_cell_circumradius=0.2, odt=True, lloyd=True, verbose=False
)
```#### Other primitive shapes
pygalmesh provides out-of-the-box support for balls, cuboids, ellipsoids, tori, cones,
cylinders, and tetrahedra. Try for example```python
import pygalmeshs0 = 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
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```python
import pygalmeshradius = 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.,
```python
import numpy as np
import pygalmeshradius = 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
You can of course translate, rotate, scale, and stretch any domain. Try, for example,
```python
import pygalmeshs = 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
pygalmesh lets you extrude any polygon into a 3D body. It even supports rotation
alongside!```python
import pygalmeshp = 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
Polygons in the x-z-plane can also be rotated around the z-axis to yield a rotation
body.```python
import pygalmeshp = 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
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.,```python
import pygalmeshclass 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.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 refinement
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`.```python
import numpy as np
import pygalmeshmesh = 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:```python
import pygalmeshs = 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](https://doc.cgal.org/latest/Surface_mesher/index.html) for the
options.#### Periodic volume meshes
pygalmesh also interfaces CGAL's [3D periodic
mesh generation](https://doc.cgal.org/latest/Periodic_3_mesh_3/index.html). 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:```python
import numpy as np
import pygalmeshclass 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 + 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 meshes
If you have a surface mesh at hand (like
[elephant.vtu](http://meshpro.github.io/pygalmesh/elephant.vtu)), pygalmesh generates a
volume mesh on the command line via```
pygalmesh volume-from-surface elephant.vtu out.vtk --cell-size 1.0 --odt
```(See `pygalmesh volume-from-surface -h` for all options.)
In Python, do
```python
import pygalmeshmesh = 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 files
It is also possible to generate meshes from INR voxel files, e.g.,
[here](https://github.com/CGAL/cgal/tree/master/Mesh_3/examples/Mesh_3/data) either on
the command line```
pygalmesh from-inr skull_2.9.inr out.vtu --cell-size 5.0 --odt
```(see `pygalmesh from-inr -h` for all options) or from Python
```python
import pygalmeshmesh = pygalmesh.generate_from_inr(
"skull_2.9.inr",
max_cell_circumradius=5.0,
verbose=False,
)
```#### Meshes from numpy arrays representing 3D images
| | |
| :------------------------------------------------------------------------: | :---------------------------------------------------------------------: |pygalmesh can help generating unstructed meshes from 3D numpy int arrays specifying the
subdomains. Subdomains with key `0` are not meshed.```python
import pygalmesh
import numpy as npx_ = 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**2
vol[idx] = 1
vol[~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 from [Lou et
al](http://biomedicaloptics.spiedigitallibrary.org/article.aspx?articleid=2600985)
available
[here](https://wustl.app.box.com/s/rqivtin0xcofjwlkz43acou8jknsbfx8/file/127108205145).
The phantom comprises four tissue types (background, fat, fibrograndular, skin, vascular
tissues). The generated mesh conforms to tissues interfaces.```python
import pygalmesh
import meshiowith open("MergedPhantom.DAT", "rb") as fid:
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 to _1 mm_ for the skin tissue (label `4`), _0.5 mm_ for the vascular
tissue (label `5`), and _2 mm_ for all other tissues (`default`).```python
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 remeshing
| | |
| :-------------------------------------------------------------------------: | :-------------------------------------------------------------------------: |pygalmesh can help remeshing an existing surface mesh, e.g.,
[`lion-head.off`](https://github.com/nschloe/pygalmesh/raw/gh-pages/lion-head.off). On
the command line, use```
pygalmesh remesh-surface lion-head.off out.vtu -e 0.025 -a 25 -s 0.1 -d 0.001
```(see `pygalmesh remesh-surface -h` for all options) or from Python
```python
import pygalmeshmesh = 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,
)
```### Installation
For installation, pygalmesh needs [CGAL](https://www.cgal.org/) and
[Eigen](http://eigen.tuxfamily.org/index.php?title=Main_Page) installed on your
system. They are typically available on your Linux distribution, e.g., on
Ubuntu```
sudo apt install libcgal-dev libeigen3-dev
```On MacOS with homebrew,
```
brew install cgal eigen
```After that, pygalmesh can be [installed from the Python Package
Index](https://pypi.org/project/pygalmesh/), so with```
pip install -U pygalmesh
```you can install/upgrade.
#### Troubleshooting
If pygalmesh fails to build due to `fatal error: 'Eigen/Dense' file not found`
you will need to create a symbolic link for Eigen to be detected, e.g.```
cd /usr/local/include
sudo ln -sf eigen3/Eigen Eigen
```It's possible that `eigen3` could be in `/usr/include` instead of
`/usr/local/install`.#### Manual installation
For 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, type `python3 setup.py install`. This does the trick
most the time.
- As a fallback, there's a CMake-based installation. Simply go `cmake /path/to/sources/` and `make`.### Testing
To run the pygalmesh unit tests, check out this repository and type
```
pytest
```### Background
CGAL offers two different approaches for mesh generation:
1. Meshes defined implicitly by level sets of functions.
2. 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 by
[mshr](https://bitbucket.org/fenics-project/mshr)), 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.See [here](https://github.com/nschloe/awesome-scientific-computing#meshing) for other
mesh generation tools.### License
This software is available under the [GPLv3
license](https://www.gnu.org/licenses/gpl-3.0.en.html) as well as a commercial
license. If you'd like to use this software commercially, please contact the
author.