Skip to content

Commit bdbaece

Browse files
bakpaulhugtalbotth-skam
authored
Add python modules that uses the CGAL binding (#567)
* Add python modules that uses the CGAL binding instead of the CGALPlugin to generate mesh from a pointcloud or a surfacic polyhedron * Add a SOFA scene opening and using the generated mesh with CGAL * Sort all files into a meshing repo and add a Gmsh example * Make display optionnal * Fix typo --------- Co-authored-by: hugtalbot <hugo.talbot@inria.fr> Co-authored-by: Themis Skamagkis <70031729+th-skam@users.noreply.github.com>
1 parent 617d506 commit bdbaece

File tree

8 files changed

+3951
-0
lines changed

8 files changed

+3951
-0
lines changed

examples/meshing/CGAL/README.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
This folder presents two simple modules using CGAL bindings allowing for easy tetrahedric mesh generation from either a polyhedron of its surface or a point cloud (using a convexe hull).
2+
3+
You can you this through the two mains (mesh_from_point_cloud_using_convex_hull.py and mesh_from_polyhedron.py) through command line (use -h for more information) or simply use the cgal_utils.py module if you want to build new scripts upon this work. The most important class being CGAL_Mesh_3_IO_Util heping to deal with the CGal data structure and use they results as numpy array. To see usage example, see mesh_from_polyhedron.py:CGAL_Mesh_from_polyhedron().
4+
5+
Those scripts are here to replace the components MeshGenerationFromPolyhedron and TriangularConvexHull3D from the CGALPLugin https://github.com/sofa-framework/CGALPlugin
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
from CGAL.CGAL_Polyhedron_3 import Polyhedron_3, Polyhedron_modifier
2+
from CGAL.CGAL_Mesh_3 import Mesh_3_Complex_3_in_triangulation_3
3+
from CGAL.CGAL_Mesh_3 import Polyhedral_mesh_domain_3
4+
from CGAL.CGAL_Mesh_3 import Mesh_3_parameters
5+
from CGAL.CGAL_Mesh_3 import Default_mesh_criteria
6+
from CGAL.CGAL_Kernel import Point_3
7+
from CGAL import CGAL_Mesh_3
8+
9+
import numpy as np
10+
import dataclasses
11+
from typing import List, Optional
12+
13+
from enum import Enum
14+
from pathlib import Path
15+
16+
import time
17+
18+
from vtkmodules.vtkIOGeometry import (
19+
vtkBYUReader,
20+
vtkOBJReader,
21+
vtkSTLReader
22+
)
23+
from vtkmodules.vtkIOLegacy import vtkPolyDataReader
24+
from vtkmodules.vtkIOPLY import vtkPLYReader
25+
from vtkmodules.vtkIOXML import vtkXMLPolyDataReader
26+
27+
import vtk
28+
from vtk.util import numpy_support
29+
from vtk.numpy_interface import algorithms as algs
30+
31+
timestamp = {}
32+
33+
def tic(hash = 0):
34+
global timestamp
35+
timestamp[hash] = time.time_ns()
36+
37+
def toc(hash = 0):
38+
global timestamp
39+
return f"{((time.time_ns() - timestamp[hash])/1000000):.3f} miliseconds"
40+
41+
42+
def ReadPolyData(file_name):
43+
#FROM Vtk examples https://examples.vtk.org/site/Python/Snippets/ReadPolyData/
44+
45+
valid_suffixes = ['.g', '.obj', '.stl', '.ply', '.vtk', '.vtp']
46+
47+
path = Path(file_name)
48+
if path.suffix:
49+
ext = path.suffix.lower()
50+
if path.suffix not in valid_suffixes:
51+
print(f'No reader for this file suffix: {ext}')
52+
return None
53+
else:
54+
if ext == ".ply":
55+
reader = vtkPLYReader()
56+
reader.SetFileName(file_name)
57+
reader.Update()
58+
poly_data = reader.GetOutput()
59+
elif ext == ".vtp":
60+
reader = vtkXMLPolyDataReader()
61+
reader.SetFileName(file_name)
62+
reader.Update()
63+
poly_data = reader.GetOutput()
64+
elif ext == ".obj":
65+
reader = vtkOBJReader()
66+
reader.SetFileName(file_name)
67+
reader.Update()
68+
poly_data = reader.GetOutput()
69+
elif ext == ".stl":
70+
reader = vtkSTLReader()
71+
reader.SetFileName(file_name)
72+
reader.Update()
73+
poly_data = reader.GetOutput()
74+
elif ext == ".vtk":
75+
reader = vtkPolyDataReader()
76+
reader.SetFileName(file_name)
77+
reader.Update()
78+
poly_data = reader.GetOutput()
79+
elif ext == ".g":
80+
reader = vtkBYUReader()
81+
reader.SetGeometryFileName(file_name)
82+
reader.Update()
83+
poly_data = reader.GetOutput()
84+
85+
return poly_data
86+
87+
88+
class CGAL_Mesh_3_IO_Util(object):
89+
90+
points : np.array
91+
triangles : np.array
92+
tetras : np.array
93+
94+
class Elem(Enum):
95+
POINTS = 1
96+
TRIANGLES = 3
97+
TETRA = 4
98+
99+
def __init__(self,mesh):
100+
self.mesh = mesh
101+
102+
def extract(self, elems : list[Elem]):
103+
104+
print(f"Extracting data into numpy objects...")
105+
tic()
106+
107+
for elem in elems:
108+
vnbe = {}
109+
match elem:
110+
case CGAL_Mesh_3_IO_Util.Elem.TRIANGLES:
111+
for elemf in self.mesh.facets():
112+
for i in range(3):
113+
if not elemf.vertex in vnbe:
114+
vnbe[elemf.vertex(i)] = 1
115+
else:
116+
vnbe[elem.vertex(i)] += 1
117+
case CGAL_Mesh_3_IO_Util.Elem.TETRA:
118+
for elemc in self.mesh.cells():
119+
for i in range(4):
120+
if not elemc.vertex in vnbe:
121+
vnbe[elemc.vertex(i)] = 1
122+
else:
123+
vnbe[elemc.vertex(i)] += 1
124+
125+
126+
V = {}
127+
it = 0
128+
for vertice in self.mesh.triangulation().finite_vertices():
129+
if vertice in vnbe:
130+
V[vertice] = it
131+
it += 1
132+
133+
if CGAL_Mesh_3_IO_Util.Elem.POINTS in elems:
134+
self.points = np.empty((len(V),3))
135+
id = 0
136+
for key in V.keys():
137+
self.points[id][:] = [self.mesh.triangulation().point(key).x(), self.mesh.triangulation().point(key).y(), self.mesh.triangulation().point(key).z()]
138+
id+=1
139+
140+
match elem:
141+
case CGAL_Mesh_3_IO_Util.Elem.TRIANGLES:
142+
self.triangles = np.empty((self.mesh.number_of_facets(),3), dtype=np.int32)
143+
id = 0
144+
for elemt in self.mesh.facets():
145+
self.triangles[id][:] = [V[elemt.vertex(0)],V[elemt.vertex(1)],V[elemt.vertex(2)]]
146+
id+= 1
147+
case CGAL_Mesh_3_IO_Util.Elem.TETRA:
148+
self.tetras = np.empty((self.mesh.number_of_cells(),4), dtype=np.int32)
149+
id = 0
150+
for elemc in self.mesh.cells():
151+
self.tetras[id][:] = [V[elemc.vertex(0)],V[elemc.vertex(1)],V[elemc.vertex(2)], V[elemc.vertex(3)]]
152+
id += 1
153+
print(f"Done ! Took {toc()}")
154+
if CGAL_Mesh_3_IO_Util.Elem.TRIANGLES in elems:
155+
print(f"Extracted {self.points.shape[0]} points and {self.triangles.shape[0]} triangles")
156+
if CGAL_Mesh_3_IO_Util.Elem.TETRA in elems:
157+
print(f"Extracted {self.points.shape[0]} points and {self.tetras.shape[0]} tetras")
158+
159+
160+
161+
def write_out(self, filename):
162+
path = Path(filename)
163+
ext = path.suffix.lower()
164+
if ext != ".vtk" and ext != ".vtu":
165+
print("Only vtk or vtu extension are suported")
166+
return
167+
168+
if (not "tetras" in self.__dict__) and ext == ".vtu":
169+
print("VTU only supported for tetrahedral meshes. Use vtk instead")
170+
return
171+
172+
print(f"Saving into {filename}...")
173+
tic()
174+
175+
if "tetras" in self.__dict__:
176+
ugrid = vtk.vtkUnstructuredGrid()
177+
else:
178+
ugrid = vtk.vtkPolyData()
179+
180+
vtk_points = vtk.vtkPoints()
181+
vtk_points.SetData(numpy_support.numpy_to_vtk(self.points, deep=True))
182+
ugrid.SetPoints(vtk_points)
183+
184+
185+
if "triangles" in self.__dict__:
186+
for triangle in self.triangles:
187+
vtkTriangleObj = vtk.vtkTriangle()
188+
vtkTriangleObj.GetPointIds().SetId(0,triangle[0])
189+
vtkTriangleObj.GetPointIds().SetId(1,triangle[1])
190+
vtkTriangleObj.GetPointIds().SetId(2,triangle[2])
191+
ugrid.InsertNextCell(vtkTriangleObj.GetCellType(), vtkTriangleObj.GetPointIds())
192+
if "tetras" in self.__dict__:
193+
for tetra in self.tetras:
194+
vtkTetraObj = vtk.vtkTetra()
195+
vtkTetraObj.GetPointIds().SetId(0,tetra[0])
196+
vtkTetraObj.GetPointIds().SetId(1,tetra[1])
197+
vtkTetraObj.GetPointIds().SetId(2,tetra[2])
198+
vtkTetraObj.GetPointIds().SetId(3,tetra[3])
199+
ugrid.InsertNextCell(vtkTetraObj.GetCellType(), vtkTetraObj.GetPointIds())
200+
201+
202+
if "tetras" in self.__dict__:
203+
writer = vtk.vtkUnstructuredGridWriter()
204+
else:
205+
writer = vtk.vtkPolyDataWriter()
206+
writer.SetFileVersion(42)
207+
writer.SetInputData(ugrid)
208+
writer.SetFileName(filename)
209+
writer.Write()
210+
print(f"Done ! Took {toc()}")
211+
212+
213+
214+
215+
216+
class CGAL_Mesh_from(object):
217+
218+
class Refiner(Enum):
219+
NONE = 0
220+
LLOYD = 1
221+
ODT = 3
222+
PERTURB = 4
223+
224+
@dataclasses.dataclass
225+
class Refiner_input():
226+
refiner_type : Enum
227+
228+
time_limit : Optional[float] = 20.0 #(ALL) to set up, in seconds, a CPU time limit after which the optimization process is stopped. This time is measured using CGAL::Real_timer. 0 means that there is no time limit.
229+
max_iteration_number : Optional[int] = 200 #(LLOYD & ODT only) limit on the number of performed iterations. 0 means that there is no limit on the number of performed iterations.
230+
convergence : Optional[int] = 0.0 #(LLOYD & ODT only) threshold ratio of stopping criterion based on convergence: the optimization process is stopped when at the last iteration the displacement of any vertex is less than a given fraction of the length of the shortest edge incident to that vertex.
231+
free_bound : Optional[bool] = False #(LLOYD & ODT only) designed to reduce running time of each optimization iteration. Any vertex that has a displacement less than a given fraction of the length of its shortest incident edge, is frozen (i.e. is not relocated). The parameter freeze_bound gives the threshold ratio. If it is set to 0, freezing of vertices is disabled.
232+
silver_bound : Optional[bool] = False #(PERTURB only) is designed to give, in degrees, a targeted lower bound on dihedral angles of mesh cells. The exudation process considers in turn all the mesh cells that have a smallest dihedral angle less than sliver_bound and tries to make them disappear by weighting their vertices. The optimization process stops when every cell in the mesh achieves this quality. The default value is 0 and means that there is no targeted bound: the exuder then runs as long as it can improve the smallest dihedral angles of the set of cells incident to some vertices.
233+
234+
235+
IOUtil : CGAL_Mesh_3_IO_Util
236+
237+
def __init__(self):
238+
pass
239+
240+
def generate(self):
241+
pass
242+
243+
244+
def __getattr__(self, name):
245+
match name:
246+
case "points":
247+
return self.IOUtil.points
248+
case "triangles":
249+
return self.IOUtil.triangles
250+
case "tetras":
251+
return self.IOUtil.tetras
252+
case _:
253+
if name in self.__dict__:
254+
return self.__dict__[name]
255+
else:
256+
raise AttributeError()
257+
258+
259+
313 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)