Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/CGAL/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
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).

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

Those scripts are here to replace the components MeshGenerationFromPolyhedron and TriangularConvexHull3D from the CGALPLugin https://github.com/sofa-framework/CGALPlugin
259 changes: 259 additions & 0 deletions examples/CGAL/cgal_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
from CGAL.CGAL_Polyhedron_3 import Polyhedron_3, Polyhedron_modifier
from CGAL.CGAL_Mesh_3 import Mesh_3_Complex_3_in_triangulation_3
from CGAL.CGAL_Mesh_3 import Polyhedral_mesh_domain_3
from CGAL.CGAL_Mesh_3 import Mesh_3_parameters
from CGAL.CGAL_Mesh_3 import Default_mesh_criteria
from CGAL.CGAL_Kernel import Point_3
from CGAL import CGAL_Mesh_3

import numpy as np
import dataclasses
from typing import List, Optional

from enum import Enum
from pathlib import Path

import time

from vtkmodules.vtkIOGeometry import (
vtkBYUReader,
vtkOBJReader,
vtkSTLReader
)
from vtkmodules.vtkIOLegacy import vtkPolyDataReader
from vtkmodules.vtkIOPLY import vtkPLYReader
from vtkmodules.vtkIOXML import vtkXMLPolyDataReader

import vtk
from vtk.util import numpy_support
from vtk.numpy_interface import algorithms as algs

timestamp = {}

def tic(hash = 0):
global timestamp
timestamp[hash] = time.time_ns()

def toc(hash = 0):
global timestamp
return f"{((time.time_ns() - timestamp[hash])/1000000):.3f} miliseconds"


def ReadPolyData(file_name):
#FROM Vtk examples https://examples.vtk.org/site/Python/Snippets/ReadPolyData/

valid_suffixes = ['.g', '.obj', '.stl', '.ply', '.vtk', '.vtp']

path = Path(file_name)
if path.suffix:
ext = path.suffix.lower()
if path.suffix not in valid_suffixes:
print(f'No reader for this file suffix: {ext}')
return None
else:
if ext == ".ply":
reader = vtkPLYReader()
reader.SetFileName(file_name)
reader.Update()
poly_data = reader.GetOutput()
elif ext == ".vtp":
reader = vtkXMLPolyDataReader()
reader.SetFileName(file_name)
reader.Update()
poly_data = reader.GetOutput()
elif ext == ".obj":
reader = vtkOBJReader()
reader.SetFileName(file_name)
reader.Update()
poly_data = reader.GetOutput()
elif ext == ".stl":
reader = vtkSTLReader()
reader.SetFileName(file_name)
reader.Update()
poly_data = reader.GetOutput()
elif ext == ".vtk":
reader = vtkPolyDataReader()
reader.SetFileName(file_name)
reader.Update()
poly_data = reader.GetOutput()
elif ext == ".g":
reader = vtkBYUReader()
reader.SetGeometryFileName(file_name)
reader.Update()
poly_data = reader.GetOutput()

return poly_data


class CGAL_Mesh_3_IO_Util(object):

points : np.array
triangles : np.array
tetras : np.array

class Elem(Enum):
POINTS = 1
TRIANGLES = 3
TETRA = 4

def __init__(self,mesh):
self.mesh = mesh

def extract(self, elems : list[Elem]):

print(f"Extracting data into numpy objects...")
tic()

for elem in elems:
vnbe = {}
match elem:
case CGAL_Mesh_3_IO_Util.Elem.TRIANGLES:
for elemf in self.mesh.facets():
for i in range(3):
if not elemf.vertex in vnbe:
vnbe[elemf.vertex(i)] = 1
else:
vnbe[elem.vertex(i)] += 1
case CGAL_Mesh_3_IO_Util.Elem.TETRA:
for elemc in self.mesh.cells():
for i in range(4):
if not elemc.vertex in vnbe:
vnbe[elemc.vertex(i)] = 1
else:
vnbe[elemc.vertex(i)] += 1


V = {}
it = 0
for vertice in self.mesh.triangulation().finite_vertices():
if vertice in vnbe:
V[vertice] = it
it += 1

if CGAL_Mesh_3_IO_Util.Elem.POINTS in elems:
self.points = np.empty((len(V),3))
id = 0
for key in V.keys():
self.points[id][:] = [self.mesh.triangulation().point(key).x(), self.mesh.triangulation().point(key).y(), self.mesh.triangulation().point(key).z()]
id+=1

match elem:
case CGAL_Mesh_3_IO_Util.Elem.TRIANGLES:
self.triangles = np.empty((self.mesh.number_of_facets(),3), dtype=np.int32)
id = 0
for elemt in self.mesh.facets():
self.triangles[id][:] = [V[elemt.vertex(0)],V[elemt.vertex(1)],V[elemt.vertex(2)]]
id+= 1
case CGAL_Mesh_3_IO_Util.Elem.TETRA:
self.tetras = np.empty((self.mesh.number_of_cells(),4), dtype=np.int32)
id = 0
for elemc in self.mesh.cells():
self.tetras[id][:] = [V[elemc.vertex(0)],V[elemc.vertex(1)],V[elemc.vertex(2)], V[elemc.vertex(3)]]
id += 1
print(f"Done ! Took {toc()}")
if CGAL_Mesh_3_IO_Util.Elem.TRIANGLES in elems:
print(f"Extracted {self.points.shape[0]} points and {self.triangles.shape[0]} triangles")
if CGAL_Mesh_3_IO_Util.Elem.TETRA in elems:
print(f"Extracted {self.points.shape[0]} points and {self.tetras.shape[0]} tetras")



def write_out(self, filename):
path = Path(filename)
ext = path.suffix.lower()
if ext != ".vtk" and ext != ".vtu":
print("Only vtk or vtu extension are suported")
return

if (not "tetras" in self.__dict__) and ext == ".vtu":
print("VTU only supported for tetrahedral meshes. Use vtk instead")
return

print(f"Saving into {filename}...")
tic()

if "tetras" in self.__dict__:
ugrid = vtk.vtkUnstructuredGrid()
else:
ugrid = vtk.vtkPolyData()

vtk_points = vtk.vtkPoints()
vtk_points.SetData(numpy_support.numpy_to_vtk(self.points, deep=True))
ugrid.SetPoints(vtk_points)


if "triangles" in self.__dict__:
for triangle in self.triangles:
vtkTriangleObj = vtk.vtkTriangle()
vtkTriangleObj.GetPointIds().SetId(0,triangle[0])
vtkTriangleObj.GetPointIds().SetId(1,triangle[1])
vtkTriangleObj.GetPointIds().SetId(2,triangle[2])
ugrid.InsertNextCell(vtkTriangleObj.GetCellType(), vtkTriangleObj.GetPointIds())
if "tetras" in self.__dict__:
for tetra in self.tetras:
vtkTetraObj = vtk.vtkTetra()
vtkTetraObj.GetPointIds().SetId(0,tetra[0])
vtkTetraObj.GetPointIds().SetId(1,tetra[1])
vtkTetraObj.GetPointIds().SetId(2,tetra[2])
vtkTetraObj.GetPointIds().SetId(3,tetra[3])
ugrid.InsertNextCell(vtkTetraObj.GetCellType(), vtkTetraObj.GetPointIds())


if "tetras" in self.__dict__:
writer = vtk.vtkUnstructuredGridWriter()
else:
writer = vtk.vtkPolyDataWriter()
writer.SetFileVersion(42)
writer.SetInputData(ugrid)
writer.SetFileName(filename)
writer.Write()
print(f"Done ! Took {toc()}")





class CGAL_Mesh_from(object):

class Refiner(Enum):
NONE = 0
LLOYD = 1
ODT = 3
PERTURB = 4

@dataclasses.dataclass
class Refiner_input():
refiner_type : Enum

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


IOUtil : CGAL_Mesh_3_IO_Util

def __init__(self):
pass

def generate(self):
pass


def __getattr__(self, name):
match name:
case "points":
return self.IOUtil.points
case "triangles":
return self.IOUtil.triangles
case "tetras":
return self.IOUtil.tetras
case _:
if name in self.__dict__:
return self.__dict__[name]
else:
raise AttributeError()



Binary file added examples/CGAL/data/point_cloud.pcd
Binary file not shown.
Loading
Loading