Skip to content

Commit 82e28b9

Browse files
authored
Merge pull request #300 from libigl/claude-edits
Add centroid binding and test [claude generated]
2 parents a864d1e + 71e4bd9 commit 82e28b9

File tree

4 files changed

+86
-1
lines changed

4 files changed

+86
-1
lines changed

memory/MEMORY.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# libigl-python-bindings memory
2+
3+
## Project
4+
Python bindings for libigl C++ geometry library using nanobind + scikit-build-core.
5+
C++ libigl source lives at `build/_deps/libigl-src/include/igl/` (fetched by CMake).
6+
7+
## Binding Architecture
8+
- Each `src/<name>.cpp` → corresponds to `build/_deps/libigl-src/include/igl/<name>.h`
9+
- CMake globs all `.cpp` files, generates `BINDING_DECLARATIONS.in` / `BINDING_INVOCATIONS.in`, `module.cpp` includes them — adding a `.cpp` auto-registers it
10+
- Types centralized in `include/default_types.h`: `Numeric=double`, `Integer=int64_t`, `RowMajor`
11+
- Zero-copy input: `nb::DRef<const Eigen::MatrixXN>` (requires C-contiguous float64)
12+
- Multiple C++ output params → `std::make_tuple(...)` → Python tuple unpacking
13+
- Sparse output: `Eigen::SparseMatrixN` + `<nanobind/eigen/sparse.h>` → scipy CSR automatically
14+
15+
## Submodules
16+
- `src/``igl` (core, MPL2)
17+
- `src/copyleft/``igl.copyleft` (GPL)
18+
- `src/copyleft/cgal/``igl.copyleft.cgal`
19+
- `src/embree/``igl.embree`
20+
- `src/triangle/``igl.triangle`
21+
22+
## Key Gotchas
23+
- numpy `int32`/`float32` silently copies; always use `int64`/`float64` in tests
24+
- In-place mutation: use `Eigen::Ref<>` + `.noconvert()` on the arg
25+
- Class templates must be typedef'd to concrete type before `nb::class_<>`
26+
- Sparse inputs must be `scipy.sparse.csr_matrix`

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ build-backend = "scikit_build_core.build"
1313

1414
[project]
1515
name = "libigl"
16-
version = "2.6.2.dev0"
16+
version = "2.6.3.dev0"
1717
description = "libigl: A simple C++ geometry processing library"
1818
readme = "README.md"
1919
requires-python = ">=3.8"

src/centroid.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#include "default_types.h"
2+
#include <igl/centroid.h>
3+
#include <nanobind/nanobind.h>
4+
#include <nanobind/eigen/dense.h>
5+
#include <nanobind/stl/tuple.h>
6+
7+
namespace nb = nanobind;
8+
using namespace nb::literals;
9+
10+
namespace pyigl
11+
{
12+
auto centroid(
13+
const nb::DRef<const Eigen::MatrixXN> &V,
14+
const nb::DRef<const Eigen::MatrixXI> &F)
15+
{
16+
Eigen::VectorXN c;
17+
Numeric vol;
18+
igl::centroid(V,F,c,vol);
19+
return std::make_tuple(c,vol);
20+
}
21+
}
22+
23+
void bind_centroid(nb::module_ &m)
24+
{
25+
m.def(
26+
"centroid",
27+
&pyigl::centroid,
28+
"V"_a,
29+
"F"_a,
30+
R"(Computes the centroid and enclosed volume of a closed mesh using a surface
31+
integral.
32+
33+
@param[in] V #V by dim list of rest domain positions
34+
@param[in] F #F by 3 list of triangle indices into V
35+
@return c dim vector of centroid coordinates
36+
@return vol total volume of solid
37+
38+
\see moments
39+
)");
40+
}

tests/test_all.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,25 @@ def test_find_cross_field_singularities(icosahedron):
789789
# theorem, so we skip this check for now.
790790
# assert np.sum(singularityIndex3) == 2 * 4 # Euler characteristic * 4-rosy fields
791791

792+
def test_centroid():
793+
# Icosahedron is centered at origin, so centroid should be near zero
794+
V, F = igl.icosahedron()
795+
796+
# Overload returning both centroid and volume
797+
c, vol = igl.centroid(V, F)
798+
assert c.shape == (3,)
799+
assert c.dtype == np.float64
800+
assert isinstance(vol, float)
801+
assert np.allclose(c, np.zeros(3), atol=1e-10)
802+
assert vol > 0
803+
804+
# Known geometry: unit tetrahedron centroid should be at (0.25, 0.25, 0.25)
805+
V_tet = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]], dtype=np.float64)
806+
F_tet = np.array([[2,1,0],[1,3,0],[3,2,0],[2,3,1]], dtype=np.int64)
807+
c_tet, vol_tet = igl.centroid(V_tet, F_tet)
808+
assert np.allclose(c_tet, [0.25, 0.25, 0.25], atol=1e-10)
809+
assert np.isclose(vol_tet, 1.0/6.0, atol=1e-10)
810+
792811
def test_comb_frame_field(icosahedron):
793812
V,F = icosahedron
794813

0 commit comments

Comments
 (0)