Skip to content

Commit f1051d3

Browse files
authored
Merge pull request #112 from nschloe/2d
2d mesh generation
2 parents b5f1ffc + f6ba19d commit f1051d3

10 files changed

Lines changed: 215 additions & 32 deletions

File tree

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ include src/generate_from_inr.hpp
33
include src/generate_from_off.hpp
44
include src/generate_periodic.hpp
55
include src/generate.hpp
6+
include src/generate_2d.hpp
67
include src/generate_surface_mesh.hpp
78
include src/polygon2d.hpp
89
include src/primitives.hpp

README.md

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<p align="center">
22
<a href="https://github.com/nschloe/pygalmesh"><img alt="pygalmesh" src="https://nschloe.github.io/pygalmesh/pygalmesh-logo.svg" width="60%"></a>
3-
<p align="center">Create high-quality 3D meshes with ease.</p>
3+
<p align="center">Create high-quality meshes with ease.</p>
44
</p>
55

66
[![PyPi Version](https://img.shields.io/pypi/v/pygalmesh.svg?style=flat-square)](https://pypi.org/project/pygalmesh)
@@ -18,38 +18,29 @@
1818
[![LGTM](https://img.shields.io/lgtm/grade/python/github/nschloe/pygalmesh.svg?style=flat-square)](https://lgtm.com/projects/g/nschloe/pygalmesh)
1919
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/psf/black)
2020

21-
pygalmesh is a Python frontend to [CGAL](https://www.cgal.org/)'s [3D mesh generation
22-
capabilities](https://doc.cgal.org/latest/Mesh_3/index.html).
23-
pygalmesh makes it easy to create high-quality 3D volume meshes, periodic volume meshes,
24-
and surface meshes.
21+
pygalmesh is a Python frontend to [CGAL](https://www.cgal.org/)'s
22+
[2D](https://doc.cgal.org/latest/Mesh_2/index.html) and [3D mesh generation
23+
capabilities](https://doc.cgal.org/latest/Mesh_3/index.html). pygalmesh makes it easy
24+
to create high-quality 2D, 3D volume meshes, periodic volume meshes, and surface meshes.
2525

26-
### Background
27-
28-
CGAL offers two different approaches for mesh generation:
29-
30-
1. Meshes defined implicitly by level sets of functions.
31-
2. Meshes defined by a set of bounding planes.
32-
33-
pygalmesh provides a front-end to the first approach, which has the following advantages
34-
and disadvantages:
35-
36-
* All boundary points are guaranteed to be in the level set within any specified
37-
residual. This results in smooth curved surfaces.
38-
* Sharp intersections of subdomains (e.g., in unions or differences of sets) need to be
39-
specified manually (via feature edges, see below), which can be tedious.
26+
### Examples
4027

41-
On the other hand, the bounding-plane approach (realized by
42-
[mshr](https://bitbucket.org/fenics-project/mshr)), has the following properties:
28+
#### 2D meshes
29+
<img src="https://nschloe.github.io/pygalmesh/rect.svg" width="30%">
4330

44-
* Smooth, curved domains are approximated by a set of bounding planes, resulting in more
45-
of less visible edges.
46-
* Intersections of domains can be computed automatically, so domain unions etc. have
47-
sharp edges where they belong.
31+
CGAL generates 2D meshes from linear contraints.
32+
```python
33+
import numpy
34+
import pygalmesh
4835

49-
See [here](https://github.com/nschloe/awesome-scientific-computing#meshing) for other
50-
mesh generation tools.
36+
points = numpy.array([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]])
37+
constraints = [[0, 1], [1, 2], [2, 3], [3, 0]]
5138

52-
### Examples
39+
mesh = pygalmesh.generate_2d(points, constraints, cell_size=1.0e-1, num_lloyd_steps=10)
40+
# mesh.points, mesh.cells
41+
```
42+
The quality of the mesh isn't very good, but can be improved with
43+
[optimesh](https://github.com/nschloe/optimesh).
5344

5445
#### A simple ball
5546
<img src="https://nschloe.github.io/pygalmesh/ball.png" width="30%">
@@ -446,6 +437,34 @@ To run the pygalmesh unit tests, check out this repository and type
446437
pytest
447438
```
448439

440+
441+
### Background
442+
443+
CGAL offers two different approaches for mesh generation:
444+
445+
1. Meshes defined implicitly by level sets of functions.
446+
2. Meshes defined by a set of bounding planes.
447+
448+
pygalmesh provides a front-end to the first approach, which has the following advantages
449+
and disadvantages:
450+
451+
* All boundary points are guaranteed to be in the level set within any specified
452+
residual. This results in smooth curved surfaces.
453+
* Sharp intersections of subdomains (e.g., in unions or differences of sets) need to be
454+
specified manually (via feature edges, see below), which can be tedious.
455+
456+
On the other hand, the bounding-plane approach (realized by
457+
[mshr](https://bitbucket.org/fenics-project/mshr)), has the following properties:
458+
459+
* Smooth, curved domains are approximated by a set of bounding planes, resulting in more
460+
of less visible edges.
461+
* Intersections of domains can be computed automatically, so domain unions etc. have
462+
sharp edges where they belong.
463+
464+
See [here](https://github.com/nschloe/awesome-scientific-computing#meshing) for other
465+
mesh generation tools.
466+
467+
449468
### License
450469

451470
pygalmesh is published under the [GPLv3 license](https://www.gnu.org/licenses/gpl-3.0.en.html).

pygalmesh/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from . import _cli
2626
from .__about__ import __version__
2727
from .main import (
28+
generate_2d,
2829
generate_from_array,
2930
generate_from_inr,
3031
generate_mesh,
@@ -62,6 +63,7 @@
6263
"RingExtrude",
6364
#
6465
"generate_mesh",
66+
"generate_2d",
6567
"generate_periodic_mesh",
6668
"generate_surface_mesh",
6769
"generate_volume_mesh_from_surface_mesh",

pygalmesh/main.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import math
12
import os
23
import tempfile
34

45
import meshio
6+
import numpy
57
from _pygalmesh import (
68
SizingFieldBase,
9+
_generate_2d,
710
_generate_from_inr,
811
_generate_from_inr_with_subdomain_sizing,
912
_generate_from_off,
@@ -67,6 +70,11 @@ def generate_mesh(
6770
return mesh
6871

6972

73+
def generate_2d(points, constraints, B=math.sqrt(2), cell_size=0.0, num_lloyd_steps=0):
74+
points, cells = _generate_2d(points, constraints, B, cell_size, num_lloyd_steps)
75+
return meshio.Mesh(numpy.array(points), {"triangle": numpy.array(cells)})
76+
77+
7078
def generate_periodic_mesh(
7179
domain,
7280
bounding_cuboid,

setup.cfg

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[metadata]
22
name = pygalmesh
3-
version = 0.7.1
3+
version = 0.7.2
44
url = https://github.com/nschloe/pygalmesh
55
author = Nico Schlömer
66
author_email = nico.schloemer@gmail.com
7-
description = Python frontend to CGAL's 3D mesh generation capabilities
7+
description = Python frontend to CGAL's mesh generation capabilities
88
long_description = file: README.md
99
long_description_content_type = text/markdown
1010
license = GPL-3.0-or-later
@@ -14,7 +14,6 @@ classifiers =
1414
License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
1515
Operating System :: OS Independent
1616
Programming Language :: Python :: 3
17-
Programming Language :: Python :: 3.5
1817
Programming Language :: Python :: 3.6
1918
Programming Language :: Python :: 3.7
2019
Programming Language :: Python :: 3.8
@@ -31,7 +30,7 @@ install_requires =
3130
meshio >= 4.0.0, < 5.0.0
3231
numpy
3332
pybind11 >= 2.2
34-
python_requires = >=3.5
33+
python_requires = >=3.6
3534
3635
[options.entry_points]
3736
console_scripts =

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def __str__(self):
2323
"_pygalmesh",
2424
[
2525
"src/generate.cpp",
26+
"src/generate_2d.cpp",
2627
"src/generate_from_inr.cpp",
2728
"src/generate_from_off.cpp",
2829
"src/generate_periodic.cpp",

src/generate_2d.cpp

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#define CGAL_MESH_3_VERBOSE 1
2+
3+
#include "generate_2d.hpp"
4+
5+
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
6+
#include <CGAL/Constrained_Delaunay_triangulation_2.h>
7+
#include <CGAL/Delaunay_mesher_2.h>
8+
#include <CGAL/Delaunay_mesh_face_base_2.h>
9+
#include <CGAL/Delaunay_mesh_vertex_base_2.h>
10+
#include <CGAL/Delaunay_mesh_size_criteria_2.h>
11+
#include <CGAL/lloyd_optimize_mesh_2.h>
12+
13+
namespace pygalmesh {
14+
15+
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
16+
typedef CGAL::Delaunay_mesh_vertex_base_2<K> Vb;
17+
typedef CGAL::Delaunay_mesh_face_base_2<K> Fb;
18+
typedef CGAL::Triangulation_data_structure_2<Vb, Fb> Tds;
19+
typedef CGAL::Constrained_Delaunay_triangulation_2<K, Tds> CDT;
20+
typedef CGAL::Delaunay_mesh_size_criteria_2<CDT> Criteria;
21+
typedef CDT::Vertex_handle Vertex_handle;
22+
typedef CDT::Point Point;
23+
24+
std::tuple<std::vector<std::array<double, 2>>, std::vector<std::array<int, 3>>>
25+
generate_2d(
26+
const std::vector<std::array<double, 2>> & points,
27+
const std::vector<std::array<int, 2>> & constraints,
28+
// See
29+
// https://doc.cgal.org/latest/Mesh_2/classCGAL_1_1Delaunay__mesh__size__criteria__2.html#a58b0186eae407ba76b8f4a3d0aa85a1a
30+
// for what the bounds mean. Spoiler:
31+
// B = circumradius / shortest_edge,
32+
// relates to the smallest angle via sin(alpha_min) = 1 / (2B)
33+
// cell_size is "size",
34+
const double max_circumradius_shortest_edge_ratio,
35+
const double cell_size,
36+
const int num_lloyd_steps
37+
)
38+
{
39+
CDT cdt;
40+
// construct a constrained triangulation
41+
std::vector<Vertex_handle> vertices(points.size());
42+
int k = 0;
43+
for (auto pt: points) {
44+
vertices[k] = cdt.insert(Point(pt[0], pt[1]));
45+
k++;
46+
}
47+
for (auto c: constraints) {
48+
cdt.insert_constraint(vertices[c[0]], vertices[c[1]]);
49+
}
50+
51+
// create proper mesh
52+
CGAL::refine_Delaunay_mesh_2(
53+
cdt,
54+
Criteria(
55+
0.25 / (max_circumradius_shortest_edge_ratio * max_circumradius_shortest_edge_ratio),
56+
cell_size
57+
)
58+
);
59+
60+
if (num_lloyd_steps > 0) {
61+
CGAL::lloyd_optimize_mesh_2(
62+
cdt,
63+
CGAL::parameters::max_iteration_number = num_lloyd_steps
64+
);
65+
}
66+
67+
// convert points to vector of arrays
68+
std::map<Vertex_handle, int> vertex_index;
69+
std::vector<std::array<double, 2>> out_points(cdt.number_of_vertices());
70+
k = 0;
71+
for (auto vit = cdt.vertices_begin(); vit!= cdt.vertices_end(); ++vit) {
72+
out_points[k][0] = vit->point()[0];
73+
out_points[k][1] = vit->point()[1];
74+
vertex_index[vit] = k;
75+
k++;
76+
}
77+
78+
std::vector<std::array<int, 3>> out_cells(cdt.number_of_faces());
79+
k = 0;
80+
for (auto fit = cdt.faces_begin(); fit!= cdt.faces_end(); ++fit) {
81+
out_cells[k][0] = vertex_index[fit->vertex(0)];
82+
out_cells[k][1] = vertex_index[fit->vertex(1)];
83+
out_cells[k][2] = vertex_index[fit->vertex(2)];
84+
k++;
85+
}
86+
87+
return std::make_tuple(out_points, out_cells);
88+
}
89+
90+
} // namespace pygalmesh

src/generate_2d.hpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#ifndef GENERATE_2D_HPP
2+
#define GENERATE_2D_HPP
3+
4+
#include <memory>
5+
#include <vector>
6+
7+
namespace pygalmesh {
8+
9+
std::tuple<std::vector<std::array<double, 2>>, std::vector<std::array<int, 3>>>
10+
generate_2d(
11+
const std::vector<std::array<double, 2>> & points,
12+
const std::vector<std::array<int, 2>> & constraints,
13+
const double max_circumradius_shortest_edge_ratio,
14+
const double cell_size,
15+
const int num_lloyd_steps
16+
);
17+
18+
} // namespace pygalmesh
19+
20+
#endif // GENERATE_2D_HPP

src/pybind11.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "domain.hpp"
22
#include "generate.hpp"
3+
#include "generate_2d.hpp"
34
#include "generate_from_off.hpp"
45
#include "generate_from_inr.hpp"
56
#include "remesh_surface.hpp"
@@ -275,6 +276,14 @@ PYBIND11_MODULE(_pygalmesh, m) {
275276
py::arg("verbose") = true,
276277
py::arg("seed") = 0
277278
);
279+
m.def(
280+
"_generate_2d", &generate_2d,
281+
py::arg("points"),
282+
py::arg("constraints"),
283+
py::arg("max_circumradius_shortest_edge_ratio") = 1.41421356237,
284+
py::arg("cell_size") = 0.0,
285+
py::arg("num_lloyd_steps") = 0
286+
);
278287
m.def(
279288
"_generate_with_sizing_field", &generate_with_sizing_field,
280289
py::arg("domain"),

test/test_2d.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import numpy
2+
3+
import pygalmesh
4+
5+
6+
def test_2d():
7+
points = numpy.array([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]])
8+
constraints = [[0, 1], [1, 2], [2, 3], [3, 0]]
9+
10+
mesh = pygalmesh.generate_2d(
11+
points, constraints, cell_size=1.0e-1, num_lloyd_steps=10
12+
)
13+
14+
assert mesh.points.shape == (276, 2)
15+
assert mesh.get_cells_type("triangle").shape == (486, 3)
16+
17+
# # show mesh
18+
# import matplotlib.pyplot as plt
19+
# pts = points[cells]
20+
# for pt in pts:
21+
# plt.plot([pt[0][0], pt[1][0]], [pt[0][1], pt[1][1]], "-k")
22+
# plt.plot([pt[1][0], pt[2][0]], [pt[1][1], pt[2][1]], "-k")
23+
# plt.plot([pt[2][0], pt[0][0]], [pt[2][1], pt[0][1]], "-k")
24+
# # for pt in points:
25+
# # plt.plot(pt[0], pt[1], "or")
26+
# plt.gca().set_aspect("equal")
27+
# plt.show()
28+
29+
# mesh.points *= 100
30+
# mesh.write("rect.svg")
31+
32+
33+
if __name__ == "__main__":
34+
test_2d()

0 commit comments

Comments
 (0)