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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ __pycache__
igl.egg-info
libigl.egg-info
*.code-workspace
out.obj
out.*
test.off
.vscode

Expand All @@ -41,3 +41,4 @@ junit
dist/
igl/*
CLAUDE.md
wheelhouse/
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" ON)
option(LIBIGL_EMBREE "Build target igl::embree" ON)
option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" ON)
option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON)
option(LIBIGL_SPECTRA "Build igl::spectra bindings" ON)
option(LIBIGL_PREDICATES "Build igl::predicates bindings" ON)
FetchContent_Declare(
libigl
GIT_REPOSITORY https://github.com/libigl/libigl.git
Expand Down Expand Up @@ -182,6 +184,12 @@ endif()
if(LIBIGL_RESTRICTED_TRIANGLE)
pyigl_include("restricted" "triangle")
endif()
if(LIBIGL_SPECTRA)
pyigl_include("" "spectra")
endif()
if(LIBIGL_PREDICATES)
pyigl_include("" "predicates")
endif()



16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ libigl_version = importlib.metadata.version('libigl')
The version of libigl is defined in the [pyproject.toml](./pyproject.toml) file.


## Compiling and modifying the bindiings
## Compiling and modifying the bindings

According to the [scikit-build-core documentation](https://scikit-build-core.readthedocs.io/en/latest/configuration.html#editable-installs), the way to make an editable (incremental) build is to:

Expand Down Expand Up @@ -69,6 +69,20 @@ test in `tests/test_all.py` to ensure the binding can at least be called as
expected.


### Packaging a wheel from an existing build

If you already have a compiled build directory, you can package it into a
`.whl` without recompiling and install that wheel into other virtual
environments on the same machine:

```
python -m pip wheel --no-build-isolation --no-deps -Cbuild-dir=build -w wheelhouse .
pip install wheelhouse/libigl-*.whl
```

The `--no-deps` flag skips bundling numpy/scipy (the target environment must
have them already). No compiler or CMake is required in the target environment.

## Testing cibuildwheel locally

Install whichever version of Python from the [official website](https://www.python.org/downloads/) and then run:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ build-backend = "scikit_build_core.build"

[project]
name = "libigl"
version = "2.6.3.dev2"
version = "2.6.3.dev3"
description = "libigl: A simple C++ geometry processing library"
readme = "README.md"
requires-python = ">=3.8"
Expand Down
47 changes: 47 additions & 0 deletions src/lexicographic_triangulation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include "default_types.h"
#include <igl/lexicographic_triangulation.h>
#include <nanobind/nanobind.h>
#include <nanobind/eigen/dense.h>

namespace nb = nanobind;
using namespace nb::literals;

namespace pyigl
{
auto lexicographic_triangulation(
const Eigen::MatrixXN &P)
{
auto orient2D = [](const Numeric* pa, const Numeric* pb, const Numeric* pc) -> int
{
const Numeric cross =
(pb[0] - pa[0]) * (pc[1] - pa[1]) -
(pb[1] - pa[1]) * (pc[0] - pa[0]);
return (cross > 0) ? 1 : (cross < 0) ? -1 : 0;
};
Eigen::MatrixXi F;
try
{
igl::lexicographic_triangulation(P, orient2D, F);
}
catch(const char* e)
{
throw std::runtime_error(e);
}
return F;
}
}

void bind_lexicographic_triangulation(nb::module_ &m)
{
m.def(
"lexicographic_triangulation",
&pyigl::lexicographic_triangulation,
"P"_a,
R"(Compute a lexicographic triangulation of a 2D point set.

Uses a floating-point cross-product orientation predicate; may be inaccurate
for nearly-collinear inputs.

@param[in] P #P by 2 list of 2D vertex positions
@param[out] F #F by 3 list of face indices into P)");
}
21 changes: 21 additions & 0 deletions src/predicates/Orientation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include "default_types.h"
#include <igl/predicates/Orientation.h>
#include <nanobind/nanobind.h>

namespace nb = nanobind;
using namespace nb::literals;

void bind_Orientation(nb::module_ &m)
{
nb::enum_<igl::predicates::Orientation>(m, "Orientation")
.value("POSITIVE", igl::predicates::Orientation::POSITIVE)
.value("INSIDE", igl::predicates::Orientation::INSIDE)
.value("NEGATIVE", igl::predicates::Orientation::NEGATIVE)
.value("OUTSIDE", igl::predicates::Orientation::OUTSIDE)
.value("COLLINEAR", igl::predicates::Orientation::COLLINEAR)
.value("COPLANAR", igl::predicates::Orientation::COPLANAR)
.value("COCIRCULAR", igl::predicates::Orientation::COCIRCULAR)
.value("COSPHERICAL", igl::predicates::Orientation::COSPHERICAL)
.value("DEGENERATE", igl::predicates::Orientation::DEGENERATE)
.export_values();
}
30 changes: 30 additions & 0 deletions src/predicates/delaunay_triangulation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include "default_types.h"
#include <igl/predicates/delaunay_triangulation.h>
#include <nanobind/nanobind.h>
#include <nanobind/eigen/dense.h>

namespace nb = nanobind;
using namespace nb::literals;

namespace pyigl
{
Eigen::MatrixXi delaunay_triangulation(
const Eigen::MatrixXN &V)
{
Eigen::MatrixXi F;
igl::predicates::delaunay_triangulation(V, F);
return F;
}
}

void bind_delaunay_triangulation(nb::module_ &m)
{
m.def(
"delaunay_triangulation",
&pyigl::delaunay_triangulation,
"V"_a,
R"(Compute a Delaunay triangulation of a 2D point set using exact predicates.

@param[in] V #V by 2 list of 2D vertex positions
@return F #F by 3 list of triangle indices into V.)");
}
56 changes: 56 additions & 0 deletions src/predicates/ear_clipping.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include "default_types.h"
#include <igl/predicates/ear_clipping.h>
#include <nanobind/nanobind.h>
#include <nanobind/eigen/dense.h>
#include <nanobind/stl/tuple.h>

namespace nb = nanobind;
using namespace nb::literals;

namespace pyigl
{
std::tuple<bool, Eigen::MatrixXi> ear_clipping(
const Eigen::MatrixXN &P)
{
Eigen::MatrixXi eF;
bool ok = igl::predicates::ear_clipping(P, eF);
return {ok, eF};
}

std::tuple<Eigen::MatrixXi, Eigen::MatrixXI> ear_clipping_with_reserved(
const Eigen::MatrixXN &P,
const Eigen::MatrixXI &RT)
{
Eigen::MatrixXi eF;
Eigen::VectorXi I;
Eigen::VectorXi RT_int = RT.cast<int>();
igl::predicates::ear_clipping(P, RT_int, eF, I);
Eigen::MatrixXI I_out = I.cast<Integer>();
return {eF, I_out};
}
}

void bind_ear_clipping(nb::module_ &m)
{
m.def(
"ear_clipping",
&pyigl::ear_clipping,
"P"_a,
R"(Ear-clipping triangulation of a 2D polygon.

Reverses P if necessary so output orientation matches input.

@param[in] P #P by 2 list of 2D polygon vertices (CCW for proper mesh)
@return (ok, eF) where ok is true if mesh is proper (input is a simple polygon)
and eF is #eF by 3 list of triangle indices into P.)");
m.def(
"ear_clipping",
&pyigl::ear_clipping_with_reserved,
"P"_a, "RT"_a,
R"(Ear-clipping triangulation with reserved (non-clippable) vertices.

@param[in] P #P by 2 list of 2D polygon vertices
@param[in] RT #P list, vertices marked 1 are preserved (not clipped)
@return (eF, I) where eF is the clipped ears (original indices into P) and
I maps indices of the remaining polygon to original P indices.)");
}
49 changes: 49 additions & 0 deletions src/predicates/find_intersections.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include "default_types.h"
#include <igl/predicates/find_intersections.h>
#include <nanobind/nanobind.h>
#include <nanobind/eigen/dense.h>
#include <nanobind/stl/tuple.h>

namespace nb = nanobind;
using namespace nb::literals;

namespace pyigl
{
std::tuple<bool, Eigen::MatrixXI, Eigen::MatrixXI> find_intersections(
const Eigen::MatrixXN &V1,
const Eigen::MatrixXI &F1,
const Eigen::MatrixXN &V2,
const Eigen::MatrixXI &F2,
const bool first_only)
{
Eigen::MatrixXi IF;
Eigen::Matrix<bool, Eigen::Dynamic, 1> CP_bool;
Eigen::MatrixXi F1_int = F1.cast<int>();
Eigen::MatrixXi F2_int = F2.cast<int>();
bool found = igl::predicates::find_intersections(
V1, F1_int, V2, F2_int, first_only, IF, CP_bool);
Eigen::MatrixXI IF_out = IF.cast<Integer>();
Eigen::MatrixXI CP_out = CP_bool.cast<Integer>();
return {found, IF_out, CP_out};
}
}

void bind_find_intersections(nb::module_ &m)
{
m.def(
"find_intersections",
&pyigl::find_intersections,
"V1"_a, "F1"_a, "V2"_a, "F2"_a, "first_only"_a = false,
R"(Find intersecting triangle pairs between two meshes.

Uses AABB tree and exact predicates.

@param[in] V1 #V1 by 3 vertex positions of first mesh
@param[in] F1 #F1 by 3 triangle indices of first mesh
@param[in] V2 #V2 by 3 vertex positions of second mesh
@param[in] F2 #F2 by 3 triangle indices of second mesh
@param[in] first_only stop after finding the first intersection
@return (found, IF, CP) where found is true if any intersections exist,
IF is #IF by 2 list of intersecting face index pairs (into F1, F2),
CP is #IF list of whether each intersection is coplanar.)");
}
43 changes: 43 additions & 0 deletions src/predicates/find_self_intersections.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include "default_types.h"
#include <igl/predicates/find_self_intersections.h>
#include <nanobind/nanobind.h>
#include <nanobind/eigen/dense.h>
#include <nanobind/stl/tuple.h>

namespace nb = nanobind;
using namespace nb::literals;

namespace pyigl
{
std::tuple<bool, Eigen::MatrixXI, Eigen::MatrixXI> find_self_intersections(
const Eigen::MatrixXN &V,
const Eigen::MatrixXI &F,
const bool first_only)
{
Eigen::MatrixXi IF;
Eigen::Matrix<bool, Eigen::Dynamic, 1> CP_bool;
Eigen::MatrixXi F_int = F.cast<int>();
bool found = igl::predicates::find_self_intersections(V, F_int, first_only, IF, CP_bool);
Eigen::MatrixXI IF_out = IF.cast<Integer>();
Eigen::MatrixXI CP_out = CP_bool.cast<Integer>();
return {found, IF_out, CP_out};
}
}

void bind_find_self_intersections(nb::module_ &m)
{
m.def(
"find_self_intersections",
&pyigl::find_self_intersections,
"V"_a, "F"_a, "first_only"_a = false,
R"(Find self-intersecting triangle pairs within a mesh.

Uses AABB tree and exact predicates.

@param[in] V #V by 3 vertex positions
@param[in] F #F by 3 triangle indices
@param[in] first_only stop after finding the first intersection
@return (found, IF, CP) where found is true if any self-intersections exist,
IF is #IF by 2 list of intersecting face index pairs,
CP is #IF list of whether each intersection is coplanar.)");
}
37 changes: 37 additions & 0 deletions src/predicates/incircle.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include "default_types.h"
#include <igl/predicates/incircle.h>
#include <nanobind/nanobind.h>
#include <nanobind/eigen/dense.h>

namespace nb = nanobind;
using namespace nb::literals;

namespace pyigl
{
igl::predicates::Orientation incircle(
const Eigen::Vector2d &pa,
const Eigen::Vector2d &pb,
const Eigen::Vector2d &pc,
const Eigen::Vector2d &pd)
{
return igl::predicates::incircle(pa, pb, pc, pd);
}
}

void bind_incircle(nb::module_ &m)
{
m.def(
"incircle",
&pyigl::incircle,
"pa"_a, "pb"_a, "pc"_a, "pd"_a,
R"(Decide whether a 2D point is inside/outside/on a circle.

Uses exact arithmetic (Shewchuk predicates).

@param[in] pa 2D point on circle
@param[in] pb 2D point on circle
@param[in] pc 2D point on circle
@param[in] pd 2D query point
@return INSIDE if pd is inside the circle defined by pa,pb,pc,
OUTSIDE if outside, COCIRCULAR if exactly on the circle.)");
}
Loading
Loading