Skip to content

Commit 3f2e111

Browse files
authored
Merge pull request #41 from jf---/feat/add-missing-igl-functions
feat: add cotmatrix, grad, cut_mesh, face_components, rdp
2 parents 8a2a5bb + 19a9217 commit 3f2e111

19 files changed

+487
-3
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
Add missing igl functions to enable compas_slicer to drop the `libigl` dependency entirely:
13+
14+
- `cotmatrix` module: `trimesh_cotmatrix`, `trimesh_cotmatrix_entries`
15+
- `grad` module: `trimesh_grad` (gradient operator)
16+
- `meshing` module: `trimesh_cut_mesh`, `trimesh_face_components`
17+
- `simplify` module: `ramer_douglas_peucker` (polyline simplification)
18+
1219
### Changed
1320

1421
### Removed

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,16 @@ endfunction()
151151

152152
# === Add your modules ===
153153
add_nanobind_module(_boundaries src/boundaries.cpp)
154+
add_nanobind_module(_cotmatrix src/cotmatrix.cpp)
154155
add_nanobind_module(_curvature src/curvature.cpp)
155156
add_nanobind_module(_geodistance src/geodistance.cpp)
157+
add_nanobind_module(_grad src/grad.cpp)
156158
add_nanobind_module(_intersections src/intersections.cpp)
157159
add_nanobind_module(_isolines src/isolines.cpp)
158160
add_nanobind_module(_mapping src/mapping.cpp)
159161
add_nanobind_module(_meshing src/meshing.cpp)
160162
add_nanobind_module(_massmatrix src/massmatrix.cpp)
161163
add_nanobind_module(_parametrisation src/parametrisation.cpp)
162164
add_nanobind_module(_planarize src/planarize.cpp)
165+
add_nanobind_module(_simplify src/simplify.cpp)
163166

src/compas_libigl/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66

77

88
__all_plugins__ = [
9+
"compas_libigl.cotmatrix",
910
"compas_libigl.geodistance",
11+
"compas_libigl.grad",
1012
"compas_libigl.intersections",
1113
"compas_libigl.isolines",
1214
"compas_libigl.massmatrix",
15+
"compas_libigl.meshing",
1316
"compas_libigl.parametrisation",
1417
"compas_libigl.planarize",
15-
"compas_libigl.meshing",
18+
"compas_libigl.simplify",
1619
]

src/compas_libigl/cotmatrix.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import numpy as np
2+
from compas.plugins import plugin
3+
4+
from compas_libigl import _cotmatrix
5+
6+
7+
@plugin(category="trimesh")
8+
def trimesh_cotmatrix(M):
9+
"""Compute the cotangent Laplacian matrix of a triangle mesh.
10+
11+
Parameters
12+
----------
13+
M : tuple[list[list[float]], list[list[int]]]
14+
A mesh represented by a tuple of (vertices, faces)
15+
where vertices are 3D points and faces are triangles
16+
17+
Returns
18+
-------
19+
scipy.sparse.csc_matrix
20+
The cotangent Laplacian matrix in sparse format.
21+
"""
22+
V, F = M
23+
V = np.asarray(V, dtype=np.float64)
24+
F = np.asarray(F, dtype=np.int32)
25+
return _cotmatrix.trimesh_cotmatrix(V, F)
26+
27+
28+
@plugin(category="trimesh")
29+
def trimesh_cotmatrix_entries(M):
30+
"""Compute cotangent values for each edge in each triangle.
31+
32+
Parameters
33+
----------
34+
M : tuple[list[list[float]], list[list[int]]]
35+
A mesh represented by a tuple of (vertices, faces)
36+
where vertices are 3D points and faces are triangles
37+
38+
Returns
39+
-------
40+
numpy.ndarray
41+
A matrix of shape (F, 3) containing cotangent values per edge.
42+
For each face, contains cotan of angles opposite to edges (1,2), (2,0), (0,1).
43+
"""
44+
V, F = M
45+
V = np.asarray(V, dtype=np.float64)
46+
F = np.asarray(F, dtype=np.int32)
47+
return _cotmatrix.trimesh_cotmatrix_entries(V, F)

src/compas_libigl/grad.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import numpy as np
2+
from compas.plugins import plugin
3+
4+
from compas_libigl import _grad
5+
6+
7+
@plugin(category="trimesh")
8+
def trimesh_grad(M):
9+
"""Compute the gradient operator for a triangle mesh.
10+
11+
Parameters
12+
----------
13+
M : tuple[list[list[float]], list[list[int]]]
14+
A mesh represented by a tuple of (vertices, faces)
15+
where vertices are 3D points and faces are triangles
16+
17+
Returns
18+
-------
19+
scipy.sparse.csc_matrix
20+
The gradient operator as a sparse matrix of size (3*F, V).
21+
When multiplied by a scalar field on vertices, produces gradient
22+
vectors per face (stacked as Gx, Gy, Gz).
23+
"""
24+
V, F = M
25+
V = np.asarray(V, dtype=np.float64)
26+
F = np.asarray(F, dtype=np.int32)
27+
return _grad.trimesh_grad(V, F)

src/compas_libigl/meshing.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,53 @@ def trimesh_remesh_along_isolines(M, scalars, isovalues):
6565
ISO = np.asarray(isovalues, dtype=np.float64)
6666
V2, F2, S2, G2 = _meshing.trimesh_remesh_along_isolines(V, F, S, ISO)
6767
return V2.tolist(), F2.tolist(), S2.tolist(), G2.tolist()
68+
69+
70+
@plugin(category="trimesh")
71+
def trimesh_cut_mesh(M, cuts):
72+
"""Cut a mesh along specified edges.
73+
74+
Parameters
75+
----------
76+
M : tuple[list[list[float]], list[list[int]]]
77+
A mesh represented by a tuple of (vertices, faces)
78+
where vertices are 3D points and faces are triangles
79+
cuts : list[list[int]]
80+
A matrix of shape (F, 3) with boolean flags indicating
81+
which edges to cut. For each face, the three values
82+
correspond to edges (v0,v1), (v1,v2), (v2,v0).
83+
84+
Returns
85+
-------
86+
tuple[list[list[float]], list[list[int]]]
87+
A tuple containing
88+
* the vertices of the cut mesh (with duplicated vertices along cuts),
89+
* the faces of the cut mesh.
90+
"""
91+
V, F = M
92+
V = np.asarray(V, dtype=np.float64)
93+
F = np.asarray(F, dtype=np.int32)
94+
C = np.asarray(cuts, dtype=np.int32)
95+
Vn, Fn = _meshing.trimesh_cut_mesh(V, F, C)
96+
return Vn.tolist(), Fn.tolist()
97+
98+
99+
@plugin(category="trimesh")
100+
def trimesh_face_components(M):
101+
"""Compute connected components of mesh faces.
102+
103+
Parameters
104+
----------
105+
M : tuple[list[list[float]], list[list[int]]]
106+
A mesh represented by a tuple of (vertices, faces)
107+
where vertices are 3D points and faces are triangles
108+
109+
Returns
110+
-------
111+
list[int]
112+
Component ID per face. Faces sharing edges belong to the same component.
113+
"""
114+
V, F = M
115+
F = np.asarray(F, dtype=np.int32)
116+
C = _meshing.trimesh_face_components(F)
117+
return C.tolist()

src/compas_libigl/simplify.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import numpy as np
2+
from compas.plugins import plugin
3+
4+
from compas_libigl import _simplify
5+
6+
7+
@plugin(category="polyline")
8+
def ramer_douglas_peucker(points, threshold):
9+
"""Simplify a polyline using Ramer-Douglas-Peucker algorithm.
10+
11+
Parameters
12+
----------
13+
points : list[list[float]]
14+
A list of 3D points representing the polyline.
15+
threshold : float
16+
Maximum distance threshold for simplification.
17+
Points that deviate less than this from the simplified line are removed.
18+
19+
Returns
20+
-------
21+
tuple[list[list[float]], list[int], list[list[float]]]
22+
A tuple containing
23+
* the simplified polyline points (S),
24+
* indices in original polyline that were kept (J),
25+
* for each original point, the corresponding point on the simplified curve (Q).
26+
"""
27+
P = np.asarray(points, dtype=np.float64)
28+
S, J, Q = _simplify.ramer_douglas_peucker(P, float(threshold))
29+
return S.tolist(), J.tolist(), Q.tolist()

src/cotmatrix.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#include "cotmatrix.hpp"
2+
3+
Eigen::SparseMatrix<double>
4+
trimesh_cotmatrix(
5+
Eigen::Ref<const compas::RowMatrixXd> V,
6+
Eigen::Ref<const compas::RowMatrixXi> F
7+
) {
8+
Eigen::SparseMatrix<double> L;
9+
igl::cotmatrix(V, F, L);
10+
return L;
11+
}
12+
13+
compas::RowMatrixXd
14+
trimesh_cotmatrix_entries(
15+
Eigen::Ref<const compas::RowMatrixXd> V,
16+
Eigen::Ref<const compas::RowMatrixXi> F
17+
) {
18+
Eigen::MatrixXd C;
19+
igl::cotmatrix_entries(V, F, C);
20+
compas::RowMatrixXd C_row = C;
21+
return C_row;
22+
}
23+
24+
NB_MODULE(_cotmatrix, m) {
25+
26+
m.def(
27+
"trimesh_cotmatrix",
28+
&trimesh_cotmatrix,
29+
"Compute the cotangent Laplacian matrix for a triangle mesh.",
30+
"V"_a, "F"_a
31+
);
32+
33+
m.def(
34+
"trimesh_cotmatrix_entries",
35+
&trimesh_cotmatrix_entries,
36+
"Compute cotangent values for each edge in each triangle.",
37+
"V"_a, "F"_a
38+
);
39+
}

src/cotmatrix.hpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#pragma once
2+
3+
#include "compas.hpp"
4+
#include <igl/cotmatrix.h>
5+
#include <igl/cotmatrix_entries.h>
6+
#include <Eigen/Core>
7+
#include <Eigen/Sparse>
8+
9+
/**
10+
* Compute the cotangent Laplacian matrix of a triangle mesh.
11+
*
12+
* @param V The vertex positions of the mesh.
13+
* @param F The face indices of the mesh.
14+
* @return The cotangent Laplacian as a sparse matrix.
15+
*/
16+
Eigen::SparseMatrix<double> trimesh_cotmatrix(
17+
Eigen::Ref<const compas::RowMatrixXd> V,
18+
Eigen::Ref<const compas::RowMatrixXi> F
19+
);
20+
21+
/**
22+
* Compute the cotangent values for each edge in each triangle of a mesh.
23+
*
24+
* @param V The vertex positions of the mesh.
25+
* @param F The face indices of the mesh.
26+
* @return A matrix of size F x 3 containing cotangent values per edge.
27+
*/
28+
compas::RowMatrixXd trimesh_cotmatrix_entries(
29+
Eigen::Ref<const compas::RowMatrixXd> V,
30+
Eigen::Ref<const compas::RowMatrixXi> F
31+
);

src/grad.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#include "grad.hpp"
2+
3+
Eigen::SparseMatrix<double>
4+
trimesh_grad(
5+
Eigen::Ref<const compas::RowMatrixXd> V,
6+
Eigen::Ref<const compas::RowMatrixXi> F
7+
) {
8+
Eigen::SparseMatrix<double> G;
9+
igl::grad(V, F, G);
10+
return G;
11+
}
12+
13+
NB_MODULE(_grad, m) {
14+
15+
m.def(
16+
"trimesh_grad",
17+
&trimesh_grad,
18+
"Compute the gradient operator for a triangle mesh.",
19+
"V"_a, "F"_a
20+
);
21+
}

0 commit comments

Comments
 (0)