Skip to content

Commit bb2fbb9

Browse files
committed
add CGAL heat geodesics method option
- add get_cgal_HEAT_geodesic_distances using compas_cgal.HeatGeodesicSolver - add HEAT_CGAL enum to GeodesicsMethod - CGAL heat ~2.1s vs libigl ~3.1s in full workflow (30% faster)
1 parent bcf4a31 commit bb2fbb9

File tree

3 files changed

+51
-1
lines changed

3 files changed

+51
-1
lines changed

src/compas_slicer/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class GeodesicsMethod(str, Enum):
3030

3131
EXACT_IGL = "exact_igl"
3232
HEAT_IGL = "heat_igl"
33+
HEAT_CGAL = "heat_cgal"
3334
HEAT = "heat"
3435

3536

src/compas_slicer/pre_processing/preprocessing_utils/compound_target.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@
1313

1414
import compas_slicer.utilities as utils
1515
from compas_slicer.pre_processing.preprocessing_utils.geodesics import (
16+
get_cgal_HEAT_geodesic_distances,
1617
get_custom_HEAT_geodesic_distances,
1718
get_igl_EXACT_geodesic_distances,
1819
get_igl_HEAT_geodesic_distances,
1920
)
2021

2122
logger = logging.getLogger('logger')
2223

23-
GeodesicsMethod = Literal['exact_igl', 'heat_igl', 'heat']
24+
GeodesicsMethod = Literal['exact_igl', 'heat_igl', 'heat_cgal', 'heat']
2425
UnionMethod = Literal['min', 'smooth', 'chamfer', 'stairs']
2526

2627

@@ -151,6 +152,9 @@ def compute_geodesic_distances(self) -> None:
151152
elif self.geodesics_method == 'heat_igl':
152153
distances_lists = [get_igl_HEAT_geodesic_distances(self.mesh, vstarts) for vstarts in
153154
self.clustered_vkeys]
155+
elif self.geodesics_method == 'heat_cgal':
156+
distances_lists = [get_cgal_HEAT_geodesic_distances(self.mesh, vstarts) for vstarts in
157+
self.clustered_vkeys]
154158
elif self.geodesics_method == 'heat':
155159
distances_lists = [get_custom_HEAT_geodesic_distances(self.mesh, vstarts, str(self.OUTPUT_PATH)) for vstarts in
156160
self.clustered_vkeys]

src/compas_slicer/pre_processing/preprocessing_utils/geodesics.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
__all__ = ['get_igl_EXACT_geodesic_distances',
2424
'get_igl_HEAT_geodesic_distances',
25+
'get_cgal_HEAT_geodesic_distances',
2526
'get_custom_HEAT_geodesic_distances',
2627
'GeodesicsCache']
2728

@@ -120,6 +121,50 @@ def get_igl_HEAT_geodesic_distances(
120121
return _geodesics_cache.get_distances(mesh, vertices_start, method='heat')
121122

122123

124+
# CGAL heat method solver cache (for precomputation reuse)
125+
_cgal_solver_cache: dict[int, object] = {}
126+
127+
128+
def get_cgal_HEAT_geodesic_distances(
129+
mesh: Mesh, vertices_start: list[int]
130+
) -> NDArray[np.floating]:
131+
"""
132+
Calculate geodesic distances using CGAL heat method.
133+
134+
Uses compas_cgal's HeatGeodesicSolver which provides CGAL's Heat_method_3
135+
implementation with intrinsic Delaunay triangulation.
136+
137+
Parameters
138+
----------
139+
mesh : Mesh
140+
A compas mesh (must be triangulated).
141+
vertices_start : list[int]
142+
Source vertex indices.
143+
144+
Returns
145+
-------
146+
NDArray
147+
Minimum distance from any source to each vertex.
148+
"""
149+
from compas_cgal.geodesics import HeatGeodesicSolver
150+
151+
# Check if we have a cached solver for this mesh
152+
mesh_hash = hash((len(list(mesh.vertices())), len(list(mesh.faces()))))
153+
if mesh_hash not in _cgal_solver_cache:
154+
_cgal_solver_cache.clear() # Clear old solvers
155+
_cgal_solver_cache[mesh_hash] = HeatGeodesicSolver(mesh)
156+
157+
solver = _cgal_solver_cache[mesh_hash]
158+
159+
# Compute distances for each source and take minimum
160+
all_distances = []
161+
for source in vertices_start:
162+
distances = solver.solve([source])
163+
all_distances.append(distances)
164+
165+
return np.min(np.array(all_distances), axis=0)
166+
167+
123168
def get_custom_HEAT_geodesic_distances(
124169
mesh: Mesh,
125170
vi_sources: list[int],

0 commit comments

Comments
 (0)