Skip to main content

Python frontend to CGAL's 3D mesh generation capabilities

Project description

pygalmesh

Create high-quality 3D meshes with ease.

CircleCI Code style: black PyPI pyversions PyPi Version Debian CI GitHub stars PyPi downloads Slack

pygalmesh is a Python frontend to CGAL's 3D mesh generation capabilities. pygalmesh makes it easy to create high-quality 3D volume meshes, periodic volume meshes, and surface meshes.

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), 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 for other mesh generation tools.

Examples

A simple ball

import pygalmesh

s = pygalmesh.Ball([0, 0, 0], 1.0)
mesh = pygalmesh.generate_mesh(s, cell_size=0.2)

# mesh.points, mesh.cells, ...

You can write the mesh using meshio, e.g.,

import meshio
meshio.write("out.vtk", mesh)

The mesh generation comes with many more options, described here. Try, for example,

mesh = pygalmesh.generate_mesh(
    s,
    cell_size=0.2,
    edge_size=0.1,
    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

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, cell_size=0.1, edge_size=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

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.,

a = numpy.sqrt(radius**2 - displacement**2)
edge_size = 0.15
n = int(2*numpy.pi*a / edge_size)
circ = [
    [
        0.0,
        a * numpy.cos(i * 2*numpy.pi / n),
        a * numpy.sin(i * 2*numpy.pi / n)
    ] for i in range(n)
    ]
circ.append(circ[0])

mesh = pygalmesh.generate_mesh(
    u,
    feature_edges=[circ],
    cell_size=0.15,
    edge_size=edge_size,
    facet_angle=25,
    facet_size=0.15,
    cell_radius_edge_ratio=2.0
)

Note that the length of the polygon legs are kept in sync with the edge_size 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,

import pygalmesh

s = pygalmesh.Stretch(
    pygalmesh.Ball([0, 0, 0], 1.0),
    [1.0, 2.0, 0.0]
)

mesh = pygalmesh.generate_mesh(s, cell_size=0.1)

Extrusion of 2D polygons

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]])
edge_size = 0.1
domain = pygalmesh.Extrude(
    p,
    [0.0, 0.0, 1.0],
    0.5 * 3.14159265359,
    edge_size
)
mesh = pygalmesh.generate_mesh(
    domain,
    cell_size=0.1,
    edge_size=edge_size,
    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.

import pygalmesh

p = pygalmesh.Polygon2D([[0.5, -0.3], [1.5, -0.3], [1.0, 0.5]])
edge_size = 0.1
domain = pygalmesh.RingExtrude(p, edge_size)
mesh = pygalmesh.generate_mesh(
    domain,
    cell_size=0.1,
    edge_size=edge_size,
    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.,

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, cell_size=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

If you want to have local refinement, you can use generate_with_sizing_field. It works just like generate_mesh except that it takes a SizingFieldBase object as cell_size.

# define a cell_size function
class Field(pygalmesh.SizingFieldBase):
    def eval(self, x):
        return abs(numpy.sqrt(numpy.dot(x, x)) - 0.5) / 5 + 0.025

mesh = pygalmesh.generate_with_sizing_field(
    pygalmesh.Ball([0.0, 0.0, 0.0], 1.0),
    facet_angle=30,
    facet_size=0.1,
    facet_distance=0.025,
    cell_radius_edge_ratio=2,
    cell_size=Field(),
)

Surface meshes

If you're only after the surface of a body, pygalmesh has generate_surface_mesh for you. It offers fewer options (obviously, cell_size is gone), but otherwise works the same way:

import pygalmesh

s = pygalmesh.Ball([0, 0, 0], 1.0)
mesh = pygalmesh.generate_surface_mesh(
    s,
    angle_bound=30,
    radius_bound=0.1,
    distance_bound=0.1
)

Refer to CGAL's documention for the options.

Periodic volume meshes

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 pygalmesh

class Schwarz(pygalmesh.DomainBase):
    def __init__(self):
        super().__init__()

    def eval(self, x):
        x2 = numpy.cos(x[0] * 2 * numpy.pi)
        y2 = numpy.cos(x[1] * 2 * numpy.pi)
        z2 = numpy.cos(x[2] * 2 * numpy.pi)
        return x2 + y2 + z2

mesh = pygalmesh.generate_periodic_mesh(
    Schwarz(),
    [0, 0, 0, 1, 1, 1],
    cell_size=0.05,
    facet_angle=30,
    facet_size=0.05,
    facet_distance=0.025,
    cell_radius_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), 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

import pygalmesh

mesh = pygalmesh.generate_volume_mesh_from_surface_mesh(
    "elephant.vtu",
    facet_angle=25.0,
    facet_size=0.15,
    facet_distance=0.008,
    cell_radius_edge_ratio=3.0,
    verbose=False
)

Meshes from INR voxel files

It is also possible to generate meshes from INR voxel files, e.g., skull_2.9.inr 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

import pygalmesh

mesh = pygalmesh.generate_from_inr(
    "skull_2.9.inr",
    cell_size=5.0,
    verbose=False,
)

Meshes from numpy array representing 3D images

pygalmesh can help generating unstructed meshes from 3D numpy arrays.

The code below creates a mesh from the 3D breast phantom from Lou et al available here. The phantom comprises four tissue types (background, fat, fibrograndular, skin, vascular tissues). The generated mesh conforms to tissues interfaces.

import pygalmesh
import meshio

Nx = 722
Ny = 411
Nz = 284
h = [0.2]*3
    
with open('MergedPhantom.DAT', 'rb') as fid:
    vol = np.fromfile(fid, dtype=np.uint8)

vol = vol.reshape((Nx,Ny,Nz))

       
mesh = pygalmesh.generate_from_array(vol, h, facet_distance=.2, cell_size=1.)
meshio.write('breast.vtk',mesh)

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).

cell_sizes_map = {'default': 2., 4: 1., 5: .5}
mesh = pygalmesh.generate_from_array_with_subdomain_sizing(
           vol, h, facet_distance=.2, cell_sizes_map=cell_sizes_map)
meshio.write('breast_adapted.vtk',mesh)

Surface remeshing

pygalmesh can help remeshing an existing surface mesh, e.g., 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

import pygalmesh

mesh = pygalmesh.remesh_surface(
    "lion-head.off",
    edge_size=0.025,
    facet_angle=25,
    facet_size=0.1,
    facet_distance=0.001,
    verbose=False,
)

Installation

For installation, pygalmesh needs CGAL and Eigen installed on your system. They are typically available on your Linux distribution, e.g., on Ubuntu

sudo apt install libcgal-dev libeigen3-dev

After that, pygalmesh can be installed from the Python Package Index, so with

pip install -U pygalmesh

you can install/upgrade.

meshio (pip install meshio) can be helpful in processing the meshes.

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

License

pygalmesh is published under the GPLv3 license.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pygalmesh-0.6.5.tar.gz (1.2 MB view details)

Uploaded Source

File details

Details for the file pygalmesh-0.6.5.tar.gz.

File metadata

  • Download URL: pygalmesh-0.6.5.tar.gz
  • Upload date:
  • Size: 1.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/46.1.3 requests-toolbelt/0.9.1 tqdm/4.45.0 CPython/3.8.2

File hashes

Hashes for pygalmesh-0.6.5.tar.gz
Algorithm Hash digest
SHA256 9257a03a86a585999db4f927920a96751b49f7538963d9ce157c38304c16c278
MD5 134543fb0b9670aa06674b72fa3dbb80
BLAKE2b-256 84e5d2e1a8d761fd1cd420988cf7f1dbc7b9c3dc55538da82141c880109ab685

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page