Skip to content

Commit 1d9e625

Browse files
FIX windows temporary fix for geodesics.
1 parent e382c14 commit 1d9e625

File tree

2 files changed

+241
-3
lines changed

2 files changed

+241
-3
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ option(ENABLE_PRECOMPILED_HEADERS "Enable precompiled headers for the build" ON)
2020
if(MSVC)
2121
set(CMAKE_GENERATOR_PLATFORM x64)
2222
add_compile_options(/Zm1200)
23+
# Disable problematic libigl features on Windows
24+
add_definitions(-DIGL_NO_EXACT_GEODESIC)
2325
endif()
2426

2527
# =====================================================================

src/compas_libigl/meshing.py

Lines changed: 239 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import numpy as np
22
from compas.plugins import plugin
3+
import sys
4+
import platform
35

4-
from compas_libigl import _meshing
6+
try:
7+
from compas_libigl import _meshing
8+
HAS_MESHING_MODULE = True
9+
except ImportError:
10+
HAS_MESHING_MODULE = False
11+
print("Warning: _meshing module could not be imported, using pure Python fallbacks")
512

613

714
@plugin(category="trimesh")
@@ -41,7 +48,11 @@ def trimesh_remesh_along_isoline(M, scalars, isovalue):
4148
S = np.asarray(scalars, dtype=np.float64)
4249
isovalue = float(isovalue)
4350

44-
return _meshing.trimesh_remesh_along_isoline(V, F, S, isovalue)
51+
if HAS_MESHING_MODULE:
52+
return _meshing.trimesh_remesh_along_isoline(V, F, S, isovalue)
53+
else:
54+
# Pure Python fallback implementation
55+
return _pure_python_trimesh_remesh_along_isoline(V, F, S, isovalue)
4556

4657

4758
@plugin(category="trimesh")
@@ -83,4 +94,229 @@ def trimesh_remesh_along_isolines(M, scalars, isovalues):
8394
S = np.asarray(scalars, dtype=np.float64)
8495
values = np.asarray(isovalues, dtype=np.float64)
8596

86-
return _meshing.trimesh_remesh_along_isolines(V, F, S, values)
97+
if HAS_MESHING_MODULE:
98+
return _meshing.trimesh_remesh_along_isolines(V, F, S, values)
99+
else:
100+
# Pure Python fallback implementation
101+
return sequential_isoline_remeshing(V, F, S, values)
102+
103+
104+
def sequential_isoline_remeshing(V, F, S, isovalues):
105+
"""Pure Python implementation of sequential isoline remeshing.
106+
107+
This is used as a fallback when the C++ implementation is not available.
108+
"""
109+
current_V = V.copy()
110+
current_F = F.copy()
111+
current_S = S.copy()
112+
113+
# Sort isovalues to ensure consistent results
114+
sorted_isovalues = sorted(isovalues)
115+
116+
for isovalue in sorted_isovalues:
117+
# Remesh along current isoline
118+
if HAS_MESHING_MODULE:
119+
result = _meshing.trimesh_remesh_along_isoline(current_V, current_F, current_S, isovalue)
120+
else:
121+
result = _pure_python_trimesh_remesh_along_isoline(current_V, current_F, current_S, isovalue)
122+
123+
# Update the current mesh for the next iteration
124+
current_V = result[0]
125+
current_F = result[1]
126+
# We need to interpolate scalar values for new vertices
127+
current_S = _interpolate_vertex_values(current_V, result[0], current_S)
128+
129+
return current_V, current_F, current_S
130+
131+
132+
def _interpolate_vertex_values(old_vertices, new_vertices, old_values):
133+
"""Interpolate scalar values for new vertices based on nearest original vertices."""
134+
# Simple implementation for fallback - in practice you'd want to use proper interpolation
135+
# based on the barycentric coordinates, but this is sufficient for a fallback
136+
new_values = np.zeros(len(new_vertices))
137+
138+
# For each new vertex, find the closest old vertex and use its value
139+
for i, new_v in enumerate(new_vertices):
140+
min_dist = float('inf')
141+
min_idx = 0
142+
for j, old_v in enumerate(old_vertices):
143+
dist = np.sum((new_v - old_v) ** 2)
144+
if dist < min_dist:
145+
min_dist = dist
146+
min_idx = j
147+
new_values[i] = old_values[min_idx]
148+
149+
return new_values
150+
151+
152+
def _pure_python_trimesh_remesh_along_isoline(V, F, S, isovalue):
153+
"""Pure Python implementation of trimesh_remesh_along_isoline.
154+
155+
This is a simplified version that can be used when the C++ implementation is not available.
156+
It does not handle all edge cases correctly but provides basic functionality.
157+
"""
158+
from compas.geometry import intersect_line_plane
159+
from compas.datastructures import Mesh
160+
161+
# Convert numpy arrays to lists
162+
vertices = V.tolist()
163+
faces = F.tolist()
164+
scalars = S.tolist()
165+
166+
# Create a mesh
167+
mesh = Mesh.from_vertices_and_faces(vertices, faces)
168+
169+
# Assign scalar values to vertices
170+
for i, value in enumerate(scalars):
171+
mesh.vertex_attribute(i, 'scalar', value)
172+
173+
# New geometry to collect
174+
new_vertices = []
175+
new_faces = []
176+
labels = []
177+
178+
# Map original vertices to new indices
179+
vertex_map = {}
180+
181+
# Create a plane from the isovalue
182+
# Assuming isovalue is in the z-direction for simplicity
183+
plane = ([0, 0, isovalue], [0, 0, 1])
184+
185+
# Process each original vertex
186+
for i in range(len(vertices)):
187+
vertex = vertices[i]
188+
scalar = scalars[i]
189+
190+
# Add to new vertices and update map
191+
new_idx = len(new_vertices)
192+
new_vertices.append(vertex)
193+
vertex_map[i] = new_idx
194+
195+
# Determine label based on scalar value
196+
label = 0 if scalar < isovalue else 1
197+
labels.append(label)
198+
199+
# Process each face to add intersection points
200+
edge_intersections = {}
201+
for face_idx, face in enumerate(faces):
202+
face_vertices = [vertices[i] for i in face]
203+
face_scalars = [scalars[i] for i in face]
204+
205+
# Check if face crosses the isovalue
206+
crosses = False
207+
for i in range(len(face)):
208+
if (face_scalars[i] < isovalue) != (face_scalars[(i+1) % len(face)] < isovalue):
209+
crosses = True
210+
break
211+
212+
if crosses:
213+
# For each edge, compute intersection if it crosses the isovalue
214+
for i in range(len(face)):
215+
v1_idx = face[i]
216+
v2_idx = face[(i+1) % len(face)]
217+
218+
# Ensure consistent ordering of edge
219+
edge = tuple(sorted([v1_idx, v2_idx]))
220+
221+
# Skip if we've already processed this edge
222+
if edge in edge_intersections:
223+
continue
224+
225+
s1 = scalars[v1_idx]
226+
s2 = scalars[v2_idx]
227+
228+
# Check if edge crosses isovalue
229+
if (s1 < isovalue) != (s2 < isovalue):
230+
# Calculate intersection point
231+
t = (isovalue - s1) / (s2 - s1)
232+
v1 = vertices[v1_idx]
233+
v2 = vertices[v2_idx]
234+
intersect_point = [
235+
v1[0] + t * (v2[0] - v1[0]),
236+
v1[1] + t * (v2[1] - v1[1]),
237+
v1[2] + t * (v2[2] - v1[2])
238+
]
239+
240+
# Add intersection point to new vertices
241+
intersect_idx = len(new_vertices)
242+
new_vertices.append(intersect_point)
243+
edge_intersections[edge] = intersect_idx
244+
245+
# Assign label based on position (on the isovalue)
246+
labels.append(0.5) # 0.5 indicates on the boundary
247+
248+
# Create new faces
249+
for face_idx, face in enumerate(faces):
250+
# Extract edges and check if any cross the isovalue
251+
edges = [(face[i], face[(i+1) % len(face)]) for i in range(len(face))]
252+
sorted_edges = [tuple(sorted(edge)) for edge in edges]
253+
crossing_edges = [edge for edge in sorted_edges if edge in edge_intersections]
254+
255+
if len(crossing_edges) == 0:
256+
# Face doesn't cross isovalue, add it directly
257+
new_face = [vertex_map[v] for v in face]
258+
new_faces.append(new_face)
259+
elif len(crossing_edges) == 2:
260+
# Face crosses isovalue, split into two new faces
261+
# Get intersection points
262+
p1_idx = edge_intersections[crossing_edges[0]]
263+
p2_idx = edge_intersections[crossing_edges[1]]
264+
265+
# Determine which vertices are on which side
266+
vertices_below = [vertex_map[v] for v in face if scalars[v] < isovalue]
267+
vertices_above = [vertex_map[v] for v in face if scalars[v] >= isovalue]
268+
269+
# Create new faces
270+
if vertices_below:
271+
for v in vertices_below:
272+
new_faces.append([v, p1_idx, p2_idx])
273+
274+
if vertices_above:
275+
for v in vertices_above:
276+
new_faces.append([v, p1_idx, p2_idx])
277+
278+
return np.array(new_vertices), np.array(new_faces), np.array(labels)
279+
280+
281+
# Function to split a mesh by a plane
282+
def split_mesh_by_plane(mesh, plane_point, plane_normal):
283+
"""Split a mesh into two parts based on a plane.
284+
285+
Parameters
286+
----------
287+
mesh : compas.datastructures.Mesh
288+
The input mesh to split.
289+
plane_point : list
290+
A point on the splitting plane.
291+
plane_normal : list
292+
The normal vector of the splitting plane.
293+
294+
Returns
295+
-------
296+
tuple
297+
A tuple containing two meshes (above_mesh, below_mesh).
298+
"""
299+
# Normalize the plane normal
300+
import numpy as np
301+
from compas.datastructures import Mesh
302+
from compas.geometry import normalize_vector, dot_vectors, distance_point_plane
303+
304+
plane_normal = normalize_vector(plane_normal)
305+
plane = (plane_point, plane_normal)
306+
307+
# Generate scalar field based on signed distance to plane
308+
vertices = mesh.vertices_attributes('xyz')
309+
scalars = [distance_point_plane((x, y, z), plane) for x, y, z in vertices]
310+
311+
# Use isoline remeshing at the zero distance (plane intersection)
312+
M = mesh.to_vertices_and_faces()
313+
V2, F2, L = trimesh_remesh_along_isoline(M, scalars, 0.0)
314+
315+
# Create meshes for positive and negative regions
316+
pos_faces = [f for i, f in enumerate(F2) if all(L[v] >= 0 for v in f)]
317+
neg_faces = [f for i, f in enumerate(F2) if all(L[v] <= 0 for v in f)]
318+
319+
above_mesh = Mesh.from_vertices_and_faces(V2, pos_faces)
320+
below_mesh = Mesh.from_vertices_and_faces(V2, neg_faces)
321+
322+
return above_mesh, below_mesh

0 commit comments

Comments
 (0)