Skip to content
Merged
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

Add missing igl functions to enable compas_slicer to drop the `libigl` dependency entirely:

- `cotmatrix` module: `trimesh_cotmatrix`, `trimesh_cotmatrix_entries`
- `grad` module: `trimesh_grad` (gradient operator)
- `meshing` module: `trimesh_cut_mesh`, `trimesh_face_components`
- `simplify` module: `ramer_douglas_peucker` (polyline simplification)

### Changed

### Removed
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,16 @@ endfunction()

# === Add your modules ===
add_nanobind_module(_boundaries src/boundaries.cpp)
add_nanobind_module(_cotmatrix src/cotmatrix.cpp)
add_nanobind_module(_curvature src/curvature.cpp)
add_nanobind_module(_geodistance src/geodistance.cpp)
add_nanobind_module(_grad src/grad.cpp)
add_nanobind_module(_intersections src/intersections.cpp)
add_nanobind_module(_isolines src/isolines.cpp)
add_nanobind_module(_mapping src/mapping.cpp)
add_nanobind_module(_meshing src/meshing.cpp)
add_nanobind_module(_massmatrix src/massmatrix.cpp)
add_nanobind_module(_parametrisation src/parametrisation.cpp)
add_nanobind_module(_planarize src/planarize.cpp)
add_nanobind_module(_simplify src/simplify.cpp)

5 changes: 4 additions & 1 deletion src/compas_libigl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@


__all_plugins__ = [
"compas_libigl.cotmatrix",
"compas_libigl.geodistance",
"compas_libigl.grad",
"compas_libigl.intersections",
"compas_libigl.isolines",
"compas_libigl.massmatrix",
"compas_libigl.meshing",
"compas_libigl.parametrisation",
"compas_libigl.planarize",
"compas_libigl.meshing",
"compas_libigl.simplify",
]
47 changes: 47 additions & 0 deletions src/compas_libigl/cotmatrix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import numpy as np
from compas.plugins import plugin

from compas_libigl import _cotmatrix


@plugin(category="trimesh")
def trimesh_cotmatrix(M):
"""Compute the cotangent Laplacian matrix of a triangle mesh.

Parameters
----------
M : tuple[list[list[float]], list[list[int]]]
A mesh represented by a tuple of (vertices, faces)
where vertices are 3D points and faces are triangles

Returns
-------
scipy.sparse.csc_matrix
The cotangent Laplacian matrix in sparse format.
"""
V, F = M
V = np.asarray(V, dtype=np.float64)
F = np.asarray(F, dtype=np.int32)
return _cotmatrix.trimesh_cotmatrix(V, F)


@plugin(category="trimesh")
def trimesh_cotmatrix_entries(M):
"""Compute cotangent values for each edge in each triangle.

Parameters
----------
M : tuple[list[list[float]], list[list[int]]]
A mesh represented by a tuple of (vertices, faces)
where vertices are 3D points and faces are triangles

Returns
-------
numpy.ndarray
A matrix of shape (F, 3) containing cotangent values per edge.
For each face, contains cotan of angles opposite to edges (1,2), (2,0), (0,1).
"""
V, F = M
V = np.asarray(V, dtype=np.float64)
F = np.asarray(F, dtype=np.int32)
return _cotmatrix.trimesh_cotmatrix_entries(V, F)
27 changes: 27 additions & 0 deletions src/compas_libigl/grad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import numpy as np
from compas.plugins import plugin

from compas_libigl import _grad


@plugin(category="trimesh")
def trimesh_grad(M):
"""Compute the gradient operator for a triangle mesh.

Parameters
----------
M : tuple[list[list[float]], list[list[int]]]
A mesh represented by a tuple of (vertices, faces)
where vertices are 3D points and faces are triangles

Returns
-------
scipy.sparse.csc_matrix
The gradient operator as a sparse matrix of size (3*F, V).
When multiplied by a scalar field on vertices, produces gradient
vectors per face (stacked as Gx, Gy, Gz).
"""
V, F = M
V = np.asarray(V, dtype=np.float64)
F = np.asarray(F, dtype=np.int32)
return _grad.trimesh_grad(V, F)
50 changes: 50 additions & 0 deletions src/compas_libigl/meshing.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,53 @@ def trimesh_remesh_along_isolines(M, scalars, isovalues):
ISO = np.asarray(isovalues, dtype=np.float64)
V2, F2, S2, G2 = _meshing.trimesh_remesh_along_isolines(V, F, S, ISO)
return V2.tolist(), F2.tolist(), S2.tolist(), G2.tolist()


@plugin(category="trimesh")
def trimesh_cut_mesh(M, cuts):
"""Cut a mesh along specified edges.

Parameters
----------
M : tuple[list[list[float]], list[list[int]]]
A mesh represented by a tuple of (vertices, faces)
where vertices are 3D points and faces are triangles
cuts : list[list[int]]
A matrix of shape (F, 3) with boolean flags indicating
which edges to cut. For each face, the three values
correspond to edges (v0,v1), (v1,v2), (v2,v0).

Returns
-------
tuple[list[list[float]], list[list[int]]]
A tuple containing
* the vertices of the cut mesh (with duplicated vertices along cuts),
* the faces of the cut mesh.
"""
V, F = M
V = np.asarray(V, dtype=np.float64)
F = np.asarray(F, dtype=np.int32)
C = np.asarray(cuts, dtype=np.int32)
Vn, Fn = _meshing.trimesh_cut_mesh(V, F, C)
return Vn.tolist(), Fn.tolist()


@plugin(category="trimesh")
def trimesh_face_components(M):
"""Compute connected components of mesh faces.

Parameters
----------
M : tuple[list[list[float]], list[list[int]]]
A mesh represented by a tuple of (vertices, faces)
where vertices are 3D points and faces are triangles

Returns
-------
list[int]
Component ID per face. Faces sharing edges belong to the same component.
"""
V, F = M
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary unpacking: The variable V is extracted from M but never used. Since trimesh_face_components only requires face connectivity (not vertex positions), you should either remove the unpacking of V or only unpack F with _, F = M for clarity.

Suggested change
V, F = M
_, F = M

Copilot uses AI. Check for mistakes.
F = np.asarray(F, dtype=np.int32)
C = _meshing.trimesh_face_components(F)
return C.tolist()
29 changes: 29 additions & 0 deletions src/compas_libigl/simplify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import numpy as np
from compas.plugins import plugin

from compas_libigl import _simplify


@plugin(category="polyline")
def ramer_douglas_peucker(points, threshold):
"""Simplify a polyline using Ramer-Douglas-Peucker algorithm.

Parameters
----------
points : list[list[float]]
A list of 3D points representing the polyline.
threshold : float
Maximum distance threshold for simplification.
Points that deviate less than this from the simplified line are removed.

Returns
-------
tuple[list[list[float]], list[int], list[list[float]]]
A tuple containing
* the simplified polyline points (S),
* indices in original polyline that were kept (J),
* for each original point, the corresponding point on the simplified curve (Q).
"""
P = np.asarray(points, dtype=np.float64)
S, J, Q = _simplify.ramer_douglas_peucker(P, float(threshold))
return S.tolist(), J.tolist(), Q.tolist()
39 changes: 39 additions & 0 deletions src/cotmatrix.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include "cotmatrix.hpp"

Eigen::SparseMatrix<double>
trimesh_cotmatrix(
Eigen::Ref<const compas::RowMatrixXd> V,
Eigen::Ref<const compas::RowMatrixXi> F
) {
Eigen::SparseMatrix<double> L;
igl::cotmatrix(V, F, L);
return L;
}

compas::RowMatrixXd
trimesh_cotmatrix_entries(
Eigen::Ref<const compas::RowMatrixXd> V,
Eigen::Ref<const compas::RowMatrixXi> F
) {
Eigen::MatrixXd C;
igl::cotmatrix_entries(V, F, C);
compas::RowMatrixXd C_row = C;
return C_row;
}

NB_MODULE(_cotmatrix, m) {
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Missing module-level documentation: The _cotmatrix module should have a m.doc() statement for consistency with other modules like _simplify (line 20 of simplify.cpp) and _meshing (line 115 of meshing.cpp). Suggested addition after line 24: m.doc() = "Cotangent matrix computation functions using libigl";

Suggested change
NB_MODULE(_cotmatrix, m) {
NB_MODULE(_cotmatrix, m) {
m.doc() = "Cotangent matrix computation functions using libigl";

Copilot uses AI. Check for mistakes.

m.def(
"trimesh_cotmatrix",
&trimesh_cotmatrix,
"Compute the cotangent Laplacian matrix for a triangle mesh.",
"V"_a, "F"_a
);

m.def(
"trimesh_cotmatrix_entries",
&trimesh_cotmatrix_entries,
"Compute cotangent values for each edge in each triangle.",
"V"_a, "F"_a
);
}
31 changes: 31 additions & 0 deletions src/cotmatrix.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include "compas.hpp"
#include <igl/cotmatrix.h>
#include <igl/cotmatrix_entries.h>
#include <Eigen/Core>
#include <Eigen/Sparse>

/**
* Compute the cotangent Laplacian matrix of a triangle mesh.
*
* @param V The vertex positions of the mesh.
* @param F The face indices of the mesh.
* @return The cotangent Laplacian as a sparse matrix.
*/
Eigen::SparseMatrix<double> trimesh_cotmatrix(
Eigen::Ref<const compas::RowMatrixXd> V,
Eigen::Ref<const compas::RowMatrixXi> F
);

/**
* Compute the cotangent values for each edge in each triangle of a mesh.
*
* @param V The vertex positions of the mesh.
* @param F The face indices of the mesh.
* @return A matrix of size F x 3 containing cotangent values per edge.
*/
compas::RowMatrixXd trimesh_cotmatrix_entries(
Eigen::Ref<const compas::RowMatrixXd> V,
Eigen::Ref<const compas::RowMatrixXi> F
);
21 changes: 21 additions & 0 deletions src/grad.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include "grad.hpp"

Eigen::SparseMatrix<double>
trimesh_grad(
Eigen::Ref<const compas::RowMatrixXd> V,
Eigen::Ref<const compas::RowMatrixXi> F
) {
Eigen::SparseMatrix<double> G;
igl::grad(V, F, G);
return G;
}

NB_MODULE(_grad, m) {
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Missing module-level documentation: The _grad module should have a m.doc() statement for consistency with other modules like _simplify (line 20 of simplify.cpp) and _meshing (line 115 of meshing.cpp). Suggested addition after line 13: m.doc() = "Gradient operator computation functions using libigl";

Suggested change
NB_MODULE(_grad, m) {
NB_MODULE(_grad, m) {
m.doc() = "Gradient operator computation functions using libigl";

Copilot uses AI. Check for mistakes.

m.def(
"trimesh_grad",
&trimesh_grad,
"Compute the gradient operator for a triangle mesh.",
"V"_a, "F"_a
);
}
18 changes: 18 additions & 0 deletions src/grad.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include "compas.hpp"
#include <igl/grad.h>
#include <Eigen/Core>
#include <Eigen/Sparse>

/**
* Compute the gradient operator for a triangle mesh.
*
* @param V The vertex positions of the mesh.
* @param F The face indices of the mesh.
* @return The gradient operator as a sparse matrix of size 3F x V.
*/
Eigen::SparseMatrix<double> trimesh_grad(
Eigen::Ref<const compas::RowMatrixXd> V,
Eigen::Ref<const compas::RowMatrixXi> F
);
38 changes: 37 additions & 1 deletion src/meshing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,28 @@ trimesh_remesh_along_isolines(
return std::make_tuple(V, F, S, face_groups);
}

std::tuple<compas::RowMatrixXd, compas::RowMatrixXi>
trimesh_cut_mesh(
Eigen::Ref<const compas::RowMatrixXd> V,
Eigen::Ref<const compas::RowMatrixXi> F,
Eigen::Ref<const compas::RowMatrixXi> cuts
) {
compas::RowMatrixXd Vn;
compas::RowMatrixXi Fn;
Eigen::VectorXi I; // birth vertices (original vertex indices)
igl::cut_mesh(V, F, cuts, Vn, Fn, I);
return std::make_tuple(Vn, Fn);
}

Eigen::VectorXi
trimesh_face_components(
Eigen::Ref<const compas::RowMatrixXi> F
) {
Eigen::VectorXi C;
igl::facet_components(F, C);
return C;
}

NB_MODULE(_meshing, m) {
m.doc() = "Mesh remeshing functions using libigl";

Expand All @@ -100,7 +122,7 @@ NB_MODULE(_meshing, m) {
"F1"_a,
"S1"_a,
"s"_a);

m.def(
"trimesh_remesh_along_isolines",
&trimesh_remesh_along_isolines,
Expand All @@ -109,4 +131,18 @@ NB_MODULE(_meshing, m) {
"F1"_a,
"S1"_a,
"values"_a);

m.def(
"trimesh_cut_mesh",
&trimesh_cut_mesh,
"Cut a mesh along specified edges, duplicating vertices.",
"V"_a,
"F"_a,
"cuts"_a);

m.def(
"trimesh_face_components",
&trimesh_face_components,
"Compute connected components of mesh faces.",
"F"_a);
}
Loading
Loading