Skip to content

Commit 37353fd

Browse files
alecjacobsonclaude
andcommitted
Add ~55 new core bindings and tests
New bindings: accumarray, barycentric_interpolation, bezier, colormap, combine, dijkstra/dijkstra_backtrack, directed_edge_parents, edge_topology, edges_to_path, euler_characteristic, exterior_edges, extract_manifold_patches, face_occurrences, fit_plane, flipped_triangles, flood_fill, hausdorff, hsv_to_rgb/rgb_to_hsv, is_boundary_edge, is_delaunay, is_irregular_vertex, iterative_closest_point, lbs_matrix, look_at, mvc, orient_outward, orientable_patches, per_corner_normals, per_edge_normals, per_vertex_attribute_smoothing, polar_dec, procrustes, pseudonormal_test, random_dir/random_dir_stratified, ray_sphere_intersect, ray_triangle_intersect, readCSV, readSTL, rgb_to_hsv, rigid_alignment, rotation_matrix_from_directions, sample_edges, segment_segment_intersect, sharp_edges, signed_angle, snap_points, solid_angle, super_fibonacci, tet_tet_adjacency, tri_tri_intersect, turning_number, uniformly_sample_two_manifold, vector_area_matrix, voronoi_mass, writeOFF, writeSTL. Also fixes extract_manifold_patches to use std::make_tuple (was std::make_pair with missing nanobind/stl/pair.h include). Adds 8 new test functions covering all new bindings; total test count goes from 48 to 56. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 71e4bd9 commit 37353fd

57 files changed

Lines changed: 2521 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/accumarray.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include "default_types.h"
2+
#include <igl/accumarray.h>
3+
#include <nanobind/nanobind.h>
4+
#include <nanobind/eigen/dense.h>
5+
6+
namespace nb = nanobind;
7+
using namespace nb::literals;
8+
9+
namespace pyigl
10+
{
11+
auto accumarray_V(
12+
const nb::DRef<const Eigen::MatrixXI> &S,
13+
const nb::DRef<const Eigen::MatrixXN> &V)
14+
{
15+
// Copy to compact column vectors so single-index (i) access works
16+
const Eigen::VectorXI s = S.reshaped();
17+
const Eigen::VectorXN v = V.reshaped();
18+
Eigen::VectorXN A;
19+
igl::accumarray(s, v, A);
20+
return A;
21+
}
22+
auto accumarray_val(
23+
const nb::DRef<const Eigen::MatrixXI> &S,
24+
const Numeric val)
25+
{
26+
const Eigen::VectorXI s = S.reshaped();
27+
Eigen::VectorXN A;
28+
igl::accumarray(s, val, A);
29+
return A;
30+
}
31+
}
32+
33+
void bind_accumarray(nb::module_ &m)
34+
{
35+
m.def("accumarray", &pyigl::accumarray_V, "S"_a, "V"_a,
36+
R"(Accumulate values V at subscripts S. Like MATLAB's accumarray.
37+
38+
@param[in] S #S list of subscripts (integer indices)
39+
@param[in] V #S list of values
40+
@return A max(S)+1 list of accumulated (summed) values)");
41+
42+
m.def("accumarray", &pyigl::accumarray_val, "S"_a, "val"_a,
43+
R"(Accumulate a constant value at subscripts S. Like MATLAB's accumarray.
44+
45+
@param[in] S #S list of subscripts (integer indices)
46+
@param[in] val scalar value to accumulate at each subscript
47+
@return A max(S)+1 list of accumulated (summed) values)");
48+
}

src/barycentric_interpolation.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#include "default_types.h"
2+
#include <igl/barycentric_interpolation.h>
3+
#include <nanobind/nanobind.h>
4+
#include <nanobind/eigen/dense.h>
5+
6+
namespace nb = nanobind;
7+
using namespace nb::literals;
8+
9+
namespace pyigl
10+
{
11+
auto barycentric_interpolation(
12+
const nb::DRef<const Eigen::MatrixXN> &D,
13+
const nb::DRef<const Eigen::MatrixXI> &F,
14+
const nb::DRef<const Eigen::MatrixXN> &B,
15+
const nb::DRef<const Eigen::MatrixXI> &I)
16+
{
17+
// I is indexed as a vector internally
18+
const Eigen::VectorXI idx = I.reshaped();
19+
Eigen::MatrixXN X;
20+
igl::barycentric_interpolation(D, F, B, idx, X);
21+
return X;
22+
}
23+
}
24+
25+
void bind_barycentric_interpolation(nb::module_ &m)
26+
{
27+
m.def("barycentric_interpolation", &pyigl::barycentric_interpolation,
28+
"D"_a, "F"_a, "B"_a, "I"_a,
29+
R"(Interpolate per-vertex data on a triangle mesh using barycentric coordinates.
30+
31+
@param[in] D #V by dim list of per-vertex data
32+
@param[in] F #F by 3 list of triangle indices
33+
@param[in] B #X by 3 list of barycentric coordinates
34+
@param[in] I #X list of triangle indices into F
35+
@return X #X by dim list of interpolated data)");
36+
}

src/bezier.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include "default_types.h"
2+
#include <igl/bezier.h>
3+
#include <nanobind/nanobind.h>
4+
#include <nanobind/eigen/dense.h>
5+
6+
namespace nb = nanobind;
7+
using namespace nb::literals;
8+
9+
namespace pyigl
10+
{
11+
// Single parameter t
12+
auto bezier_t(
13+
const nb::DRef<const Eigen::MatrixXN> &V,
14+
const Numeric t)
15+
{
16+
Eigen::MatrixXN P;
17+
igl::bezier(V, t, P);
18+
return P;
19+
}
20+
// Array of parameters T
21+
auto bezier_T(
22+
const nb::DRef<const Eigen::MatrixXN> &V,
23+
const nb::DRef<const Eigen::MatrixXN> &T)
24+
{
25+
// Copy T to compact vector for single-index access
26+
const Eigen::VectorXN t = T.reshaped();
27+
Eigen::MatrixXN P;
28+
igl::bezier(V, t, P);
29+
return P;
30+
}
31+
}
32+
33+
void bind_bezier(nb::module_ &m)
34+
{
35+
m.def("bezier", &pyigl::bezier_t, "V"_a, "t"_a,
36+
R"(Evaluate a polynomial Bezier curve at a single parameter value.
37+
38+
@param[in] V #V by dim list of Bezier control points
39+
@param[in] t evaluation parameter in [0,1]
40+
@return P 1 by dim output point)");
41+
42+
m.def("bezier", &pyigl::bezier_T, "V"_a, "T"_a,
43+
R"(Evaluate a polynomial Bezier curve at many parameter values.
44+
45+
@param[in] V #V by dim list of Bezier control points
46+
@param[in] T #T list of evaluation parameters in [0,1]
47+
@return P #T by dim output points)");
48+
}

src/colormap.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include "default_types.h"
2+
#include <igl/colormap.h>
3+
#include <nanobind/nanobind.h>
4+
#include <nanobind/eigen/dense.h>
5+
6+
namespace nb = nanobind;
7+
using namespace nb::literals;
8+
9+
namespace pyigl
10+
{
11+
// Map scalar array to RGB colors using a colormap
12+
auto colormap(
13+
const igl::ColorMapType cm,
14+
const nb::DRef<const Eigen::VectorXN> &Z,
15+
const bool normalize)
16+
{
17+
Eigen::MatrixXN C;
18+
igl::colormap(cm, Z, normalize, C);
19+
return C;
20+
}
21+
// Map scalar to single RGB triple
22+
auto colormap_scalar(
23+
const igl::ColorMapType cm,
24+
const Numeric f)
25+
{
26+
Numeric r, g, b;
27+
igl::colormap(cm, f, r, g, b);
28+
Eigen::Vector3d rgb;
29+
rgb << r, g, b;
30+
return rgb;
31+
}
32+
}
33+
34+
void bind_colormap(nb::module_ &m)
35+
{
36+
nb::enum_<igl::ColorMapType>(m, "ColorMapType")
37+
.value("INFERNO", igl::COLOR_MAP_TYPE_INFERNO)
38+
.value("JET", igl::COLOR_MAP_TYPE_JET)
39+
.value("MAGMA", igl::COLOR_MAP_TYPE_MAGMA)
40+
.value("PARULA", igl::COLOR_MAP_TYPE_PARULA)
41+
.value("PLASMA", igl::COLOR_MAP_TYPE_PLASMA)
42+
.value("VIRIDIS", igl::COLOR_MAP_TYPE_VIRIDIS)
43+
.value("TURBO", igl::COLOR_MAP_TYPE_TURBO)
44+
.export_values();
45+
46+
m.def("colormap", &pyigl::colormap,
47+
"cm"_a, "Z"_a, "normalize"_a = true,
48+
R"(Map scalar values to RGB colors using a colormap.
49+
50+
@param[in] cm colormap type (igl.ColorMapType.VIRIDIS, .JET, etc.)
51+
@param[in] Z #Z list of scalar values
52+
@param[in] normalize if true, normalize Z to [0,1] range before mapping
53+
@return C #Z by 3 list of RGB colors in [0,1])");
54+
55+
m.def("colormap", &pyigl::colormap_scalar,
56+
"cm"_a, "f"_a,
57+
R"(Map a single scalar in [0,1] to an RGB color.
58+
59+
@param[in] cm colormap type
60+
@param[in] f scalar in [0,1]
61+
@return 3-vector of RGB values in [0,1])");
62+
}

src/combine.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#include "default_types.h"
2+
#include <igl/combine.h>
3+
#include <nanobind/nanobind.h>
4+
#include <nanobind/eigen/dense.h>
5+
#include <nanobind/stl/tuple.h>
6+
#include <nanobind/stl/vector.h>
7+
8+
namespace nb = nanobind;
9+
using namespace nb::literals;
10+
11+
namespace pyigl
12+
{
13+
auto combine(
14+
const std::vector<Eigen::MatrixXN> &VV,
15+
const std::vector<Eigen::MatrixXI> &FF)
16+
{
17+
Eigen::MatrixXN V;
18+
Eigen::MatrixXI F;
19+
igl::combine(VV, FF, V, F);
20+
return std::make_tuple(V, F);
21+
}
22+
}
23+
24+
void bind_combine(nb::module_ &m)
25+
{
26+
m.def("combine", &pyigl::combine, "VV"_a, "FF"_a,
27+
R"(Concatenate multiple meshes into a single mesh.
28+
29+
@param[in] VV list of vertex position matrices, each #Vi by dim
30+
@param[in] FF list of face index matrices, each #Fi by simplex_size
31+
@return V concatenated vertex positions
32+
@return F concatenated face indices (reindexed into V))");
33+
}

src/dijkstra.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#include "default_types.h"
2+
#include <igl/dijkstra.h>
3+
#include <nanobind/nanobind.h>
4+
#include <nanobind/eigen/dense.h>
5+
#include <nanobind/stl/tuple.h>
6+
#include <nanobind/stl/vector.h>
7+
#include <nanobind/stl/set.h>
8+
9+
namespace nb = nanobind;
10+
using namespace nb::literals;
11+
12+
namespace pyigl
13+
{
14+
// Dijkstra with uniform edge weights (Euclidean distance via vertex positions)
15+
auto dijkstra(
16+
const nb::DRef<const Eigen::MatrixXN> &V,
17+
const std::vector<std::vector<Integer>> &VV,
18+
const Integer source,
19+
const std::set<Integer> &targets)
20+
{
21+
Eigen::VectorXN min_distance;
22+
Eigen::VectorXI previous;
23+
igl::dijkstra(V, VV, source, targets, min_distance, previous);
24+
return std::make_tuple(min_distance, previous);
25+
}
26+
// Dijkstra with custom vertex weights
27+
auto dijkstra_weighted(
28+
const Integer source,
29+
const std::set<Integer> &targets,
30+
const std::vector<std::vector<Integer>> &VV,
31+
const std::vector<double> &weights)
32+
{
33+
Eigen::VectorXN min_distance;
34+
Eigen::VectorXI previous;
35+
igl::dijkstra(source, targets, VV, weights, min_distance, previous);
36+
return std::make_tuple(min_distance, previous);
37+
}
38+
// Backtrack path from vertex to source
39+
auto dijkstra_backtrack(
40+
const Integer vertex,
41+
const nb::DRef<const Eigen::MatrixXI> &previous)
42+
{
43+
// previous[i] single-index access requires IsVectorAtCompileTime — copy to VectorXI
44+
const Eigen::VectorXI prev = previous.reshaped();
45+
std::vector<Integer> path;
46+
igl::dijkstra(vertex, prev, path);
47+
Eigen::VectorXI result(path.size());
48+
for (size_t i = 0; i < path.size(); ++i) result(i) = path[i];
49+
return result;
50+
}
51+
}
52+
53+
void bind_dijkstra(nb::module_ &m)
54+
{
55+
m.def("dijkstra", &pyigl::dijkstra,
56+
"V"_a, "VV"_a, "source"_a, "targets"_a,
57+
R"(Dijkstra shortest paths using Euclidean edge weights.
58+
59+
@param[in] V #V by 3 list of vertex positions
60+
@param[in] VV #V list of adjacency lists (e.g. from adjacency_list)
61+
@param[in] source source vertex index
62+
@param[in] targets set of target vertex indices (stop early if reached)
63+
@return min_distance #V list of minimum distances from source
64+
@return previous #V list of previous vertex indices for path reconstruction)");
65+
66+
m.def("dijkstra", &pyigl::dijkstra_weighted,
67+
"source"_a, "targets"_a, "VV"_a, "weights"_a,
68+
R"(Dijkstra shortest paths with custom vertex weights.
69+
70+
@param[in] source source vertex index
71+
@param[in] targets set of target vertex indices
72+
@param[in] VV #V list of adjacency lists
73+
@param[in] weights #V list of vertex weights
74+
@return min_distance #V list of minimum distances from source
75+
@return previous #V list of previous vertex indices)");
76+
77+
m.def("dijkstra_backtrack", &pyigl::dijkstra_backtrack,
78+
"vertex"_a, "previous"_a,
79+
R"(Backtrack path from a vertex to the source using Dijkstra's previous array.
80+
81+
@param[in] vertex destination vertex
82+
@param[in] previous #V list of previous vertex indices (from dijkstra)
83+
@return path list of vertex indices from vertex to source)");
84+
}

src/directed_edge_parents.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include "default_types.h"
2+
#include <igl/directed_edge_parents.h>
3+
#include <nanobind/nanobind.h>
4+
#include <nanobind/eigen/dense.h>
5+
6+
namespace nb = nanobind;
7+
using namespace nb::literals;
8+
9+
namespace pyigl
10+
{
11+
auto directed_edge_parents(const nb::DRef<const Eigen::MatrixXI> &E)
12+
{
13+
Eigen::VectorXI P;
14+
igl::directed_edge_parents(E, P);
15+
return P;
16+
}
17+
}
18+
19+
void bind_directed_edge_parents(nb::module_ &m)
20+
{
21+
m.def("directed_edge_parents", &pyigl::directed_edge_parents, "E"_a,
22+
R"(Recover parent edges in a tree given directed edges.
23+
24+
@param[in] E #E by 2 list of directed edges
25+
@return P #E list of parent indices into E (-1 means root))");
26+
}

src/edge_topology.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#include "default_types.h"
2+
#include <igl/edge_topology.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 edge_topology(
13+
const nb::DRef<const Eigen::MatrixXN> &V,
14+
const nb::DRef<const Eigen::MatrixXI> &F)
15+
{
16+
Eigen::MatrixXI EV, FE, EF;
17+
igl::edge_topology(V, F, EV, FE, EF);
18+
return std::make_tuple(EV, FE, EF);
19+
}
20+
}
21+
22+
void bind_edge_topology(nb::module_ &m)
23+
{
24+
m.def("edge_topology", &pyigl::edge_topology, "V"_a, "F"_a,
25+
R"(Initialize edges and their topological relations for an edge-manifold mesh.
26+
27+
@param[in] V #V by dim list of vertex positions (unused, kept for API compatibility)
28+
@param[in] F #F by 3 list of triangle indices
29+
@return EV #E by 2 list of edge vertex indices
30+
@return FE #F by 3 list of face-edge relations (FE(f,c) is an edge incident on corner c of face f)
31+
@return EF #E by 2 list of edge-face relations (EF(e,:) are the two adjacent faces))");
32+
}

0 commit comments

Comments
 (0)