From 2aa3cd55ff2e1605f456fbea4c88ffefa893ac1a Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Fri, 25 Feb 2022 20:03:16 -0500 Subject: [PATCH 1/6] proof of concept binding igl::copyleft::cgal::convex_hull --- CMakeLists.txt | 22 ++++++++++---- igl/__init__.py | 1 + src/copyleft/cgal/convex_hull.cpp | 48 +++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 src/copyleft/cgal/convex_hull.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b465ee0a..d9c37e87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,9 +16,12 @@ include(Warnings) include(CXXFeatures) option(PY_IGL_PYTHON_TESTS "Run Python tests" ON) +message(STATUS "PY_IGL_PYTHON_TESTS: ${PY_IGL_PYTHON_TESTS}") -include(PyiglDependencies) +option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" ON) +# libigl options must come before include(PyiglDependencies) +include(PyiglDependencies) if(NOT TARGET igl::core) include(libigl) endif() @@ -31,16 +34,23 @@ include(numpyeigen) # A module for writing bindings with our framework file(GLOB PYIGL_SOURCES src/*.cpp) - npe_add_module(pyigl BINDING_SOURCES - ${PYIGL_SOURCES} - ${PYIGL_SOURCES_COPYLEFT}) - -#TODO move additional libs to variable + ${PYIGL_SOURCES}) target_link_libraries(pyigl PRIVATE igl::core) target_include_directories(pyigl PRIVATE "src/include") +if(LIBIGL_COPYLEFT_CGAL) + file(GLOB PYIGL_COPYLEFT_CGAL_SOURCES src/copyleft/cgal/*.cpp) + npe_add_module(pyigl_copyleft_cgal + BINDING_SOURCES + ${PYIGL_COPYLEFT_CGAL_SOURCES}) + target_link_libraries(pyigl_copyleft_cgal PRIVATE igl::core igl_copyleft::cgal) + target_include_directories(pyigl_copyleft_cgal PRIVATE "src/include") + target_link_libraries(pyigl INTERFACE pyigl_copyleft_cgal) +endif() + + add_library(pyigl_classes MODULE classes/classes.cpp) target_link_libraries(pyigl_classes PRIVATE npe igl::core) target_link_libraries(pyigl_classes PRIVATE pybind11::module) diff --git a/igl/__init__.py b/igl/__init__.py index 6f8e6a4e..fdb26700 100644 --- a/igl/__init__.py +++ b/igl/__init__.py @@ -1,3 +1,4 @@ from .pyigl import * +from .pyigl_copyleft_cgal import * from .helpers import * from .pyigl_classes import * diff --git a/src/copyleft/cgal/convex_hull.cpp b/src/copyleft/cgal/convex_hull.cpp new file mode 100644 index 00000000..86c9c013 --- /dev/null +++ b/src/copyleft/cgal/convex_hull.cpp @@ -0,0 +1,48 @@ +#include +#include + +#include + +const char* ds_convex_hull = R"igl_Qu8mg5v7( + +Parameters +---------- + + +Returns +------- + + +See also +-------- + + +Notes +----- +None + +Examples +-------- + + Given a set of points (V), compute the convex hull as a triangle mesh (W,G) + + Inputs: + V #V by 3 list of input points + Outputs: + W #W by 3 list of convex hull points + G #G by 3 list of triangle indices into W +)igl_Qu8mg5v7"; + +npe_function(convex_hull) +npe_doc(ds_convex_hull) + +npe_arg(v, dense_float, dense_double) +npe_begin_code() + + EigenDenseLike w; + EigenDenseInt g; + igl::copyleft::cgal::convex_hull(v, w, g); + return std::make_tuple(npe::move(w), npe::move(g)); + +npe_end_code() + From 1a4d554a1950ee347f3b2209b8f56ac090c8025e Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 26 Feb 2022 22:44:05 -0500 Subject: [PATCH 2/6] generalize cmake for modules --- CMakeLists.txt | 43 ++++++++++++++++++++++++------- cmake/PyiglDependencies.cmake | 2 +- igl/__init__.py | 1 - scripts/generate_bindings.py | 4 +-- src/copyleft/cgal/convex_hull.cpp | 35 +++++++------------------ 5 files changed, 45 insertions(+), 40 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d9c37e87..d20e0fc6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,16 +39,39 @@ npe_add_module(pyigl ${PYIGL_SOURCES}) target_link_libraries(pyigl PRIVATE igl::core) target_include_directories(pyigl PRIVATE "src/include") - -if(LIBIGL_COPYLEFT_CGAL) - file(GLOB PYIGL_COPYLEFT_CGAL_SOURCES src/copyleft/cgal/*.cpp) - npe_add_module(pyigl_copyleft_cgal - BINDING_SOURCES - ${PYIGL_COPYLEFT_CGAL_SOURCES}) - target_link_libraries(pyigl_copyleft_cgal PRIVATE igl::core igl_copyleft::cgal) - target_include_directories(pyigl_copyleft_cgal PRIVATE "src/include") - target_link_libraries(pyigl INTERFACE pyigl_copyleft_cgal) -endif() +set_target_properties(pyigl PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") + +# don't need to worry about nested modules (opengl/** are the only ones and +# those probably aren't ever getting python bindings) +# +# prefix is either "", "copyleft", or "restricted" +function(pyigl_include prefix name) + string(TOUPPER "${prefix}" prefix_uc) + string(TOUPPER "${name}" name_uc) + if(prefix_uc) + string(PREPEND prefix_uc _) + endif() + string(TOLOWER "${prefix_uc}" prefix_lc) + if(LIBIGL${prefix_uc}_${name_uc}) + if(${prefix} STREQUAL "copyleft") + set(subpath "copyleft/${name}") + else() # "" or "restricted" + set(subpath "${name}") + endif() + file(GLOB sources src/${subpath}/*.cpp) + set(target_name "pyigl${prefix_lc}_${name}") + npe_add_module( ${target_name} BINDING_SOURCES ${sources}) + target_link_libraries( ${target_name} PRIVATE igl::core igl${prefix_lc}::${name}) + target_include_directories( ${target_name} PRIVATE "src/include") + set(output_dir "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${subpath}") + file(MAKE_DIRECTORY ${output_dir}) + file(WRITE "${output_dir}/__init__.py" "from .${target_name} import *") + set_target_properties(${target_name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${output_dir}") + target_link_libraries( pyigl INTERFACE ${target_name}) + endif() +endfunction() + +pyigl_include("copyleft" "cgal") add_library(pyigl_classes MODULE classes/classes.cpp) diff --git a/cmake/PyiglDependencies.cmake b/cmake/PyiglDependencies.cmake index 68b0840d..4bc8018b 100644 --- a/cmake/PyiglDependencies.cmake +++ b/cmake/PyiglDependencies.cmake @@ -20,7 +20,7 @@ include(FetchContent) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git - GIT_TAG v2.4.0 + GIT_TAG 4fa1e22c8f30536477d20013f555dec2d7dba119 ) FetchContent_MakeAvailable(libigl) diff --git a/igl/__init__.py b/igl/__init__.py index fdb26700..6f8e6a4e 100644 --- a/igl/__init__.py +++ b/igl/__init__.py @@ -1,4 +1,3 @@ from .pyigl import * -from .pyigl_copyleft_cgal import * from .helpers import * from .pyigl_classes import * diff --git a/scripts/generate_bindings.py b/scripts/generate_bindings.py index cc75d0d7..5f5116e3 100755 --- a/scripts/generate_bindings.py +++ b/scripts/generate_bindings.py @@ -192,7 +192,7 @@ def map_parameter_types(name, cpp_type, parsed_types, errors, enum_types): # Parse c++ header files print("Parsing header files...") - load_headers = True + load_headers = False if load_headers: with open("headers.dat", 'rb') as fs: dicts = pickle.load(fs) @@ -291,7 +291,7 @@ def map_parameter_types(name, cpp_type, parsed_types, errors, enum_types): # Write binding files try: tpl = Template(filename='basic_function.mako') - #print(correct_functions) + print(correct_functions) includes = ["", ""] rendered = tpl.render(functions=correct_functions, enums=enums, includes=includes) tpl1 = Template(filename='basic_function.mako') diff --git a/src/copyleft/cgal/convex_hull.cpp b/src/copyleft/cgal/convex_hull.cpp index 86c9c013..8e225e95 100644 --- a/src/copyleft/cgal/convex_hull.cpp +++ b/src/copyleft/cgal/convex_hull.cpp @@ -4,33 +4,15 @@ #include const char* ds_convex_hull = R"igl_Qu8mg5v7( - +Given a set of points (V), compute the convex hull as a triangle mesh (F) + Parameters ---------- - +V : #V by 3 list of input points Returns ------- - - -See also --------- - - -Notes ------ -None - -Examples --------- - - Given a set of points (V), compute the convex hull as a triangle mesh (W,G) - - Inputs: - V #V by 3 list of input points - Outputs: - W #W by 3 list of convex hull points - G #G by 3 list of triangle indices into W +F #F by 3 list of triangle indices into V )igl_Qu8mg5v7"; npe_function(convex_hull) @@ -39,10 +21,11 @@ npe_doc(ds_convex_hull) npe_arg(v, dense_float, dense_double) npe_begin_code() - EigenDenseLike w; EigenDenseInt g; - igl::copyleft::cgal::convex_hull(v, w, g); - return std::make_tuple(npe::move(w), npe::move(g)); + // when https://github.com/libigl/libigl/pull/1989 is merged this copy should + // be removed + Eigen::MatrixXd v_copy = v.template cast(); + igl::copyleft::cgal::convex_hull(v_copy, g); + return npe::move(g); npe_end_code() - From aa689801d1210cbfa6594909c5e673cadf8c0ba0 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Sat, 26 Feb 2022 23:31:58 -0500 Subject: [PATCH 3/6] mesh_boolean --- src/copyleft/cgal/mesh_boolean.cpp | 63 ++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/copyleft/cgal/mesh_boolean.cpp diff --git a/src/copyleft/cgal/mesh_boolean.cpp b/src/copyleft/cgal/mesh_boolean.cpp new file mode 100644 index 00000000..2a565f0b --- /dev/null +++ b/src/copyleft/cgal/mesh_boolean.cpp @@ -0,0 +1,63 @@ +#include +#include +#include + + +const char* ds_mesh_boolean = R"igl_Qu8mg5v7( +MESH_BOOLEAN Compute boolean csg operations on "solid", consistently oriented +meshes. + + +Parameters +---------- +VA : #VA by 3 list of vertex positions of first mesh +FA : #FA by 3 list of triangle indices into VA +VB : #VB by 3 list of vertex positions of second mesh +FB : #FB by 3 list of triangle indices into VB + +Returns +------- +VC : #VC by 3 list of vertex positions of boolean result mesh +FC : #FC by 3 list of triangle indices into VC +J : #FC list of indices into [FA;FA.rows()+FB] revealing "birth" facet + +See also +-------- +mesh_boolean_cork, intersect_other, remesh_self_intersections +)igl_Qu8mg5v7"; + +npe_function(mesh_boolean) +npe_doc(ds_mesh_boolean) + +npe_arg(va, dense_float, dense_double) +npe_arg(fa, dense_int, dense_long, dense_longlong) +npe_arg(vb, npe_matches(va)) +npe_arg(fb, npe_matches(fa)) +npe_arg(type, std::string) + + +npe_begin_code() + Eigen::MatrixXd va_copy = va.template cast(); + Eigen::MatrixXd vb_copy = vb.template cast(); + Eigen::MatrixXi fa_copy = fa.template cast(); + Eigen::MatrixXi fb_copy = fb.template cast(); + + Eigen::MatrixXd vc_copy; + Eigen::MatrixXi fc_copy; + Eigen::VectorXi j_copy; + igl::copyleft::cgal::mesh_boolean( + va_copy, + fa_copy, + vb_copy, + fb_copy, + type, + vc_copy, + fc_copy, + j_copy); + + EigenDenseLike vc = vc_copy.cast(); + EigenDenseLike fc = fc_copy.cast(); + EigenDenseLike j = j_copy.cast(); + return std::make_tuple(npe::move(vc), npe::move(fc), npe::move(j)); +npe_end_code() + From 729e513c99845256b58794403260b5f93549e0c6 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Fri, 1 Apr 2022 17:52:58 -0400 Subject: [PATCH 4/6] remesh_self_intersections --- .../cgal/remesh_self_intersections.cpp | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/copyleft/cgal/remesh_self_intersections.cpp diff --git a/src/copyleft/cgal/remesh_self_intersections.cpp b/src/copyleft/cgal/remesh_self_intersections.cpp new file mode 100644 index 00000000..074802c5 --- /dev/null +++ b/src/copyleft/cgal/remesh_self_intersections.cpp @@ -0,0 +1,79 @@ +#include +#include +#include + + + +const char* ds_remesh_self_intersections = R"igl_Qu8mg5v7( +REMESH_SELF_INTERSECTIONS Given a triangle mesh (V,F) compute a new mesh (VV,FF) +which is the same as (V,F) except that any self-intersecting triangles in (V,F) +have been subdivided (new vertices and face created) so that the +self-intersection contour lies exactly on edges in (VV,FF). New vertices will +appear in original faces or on original edges. New vertices on edges are +"merged" only across original faces sharing that edge. This means that if the +input triangle mesh is a closed manifold the output will be too. + + +Parameters +---------- +V : #V by 3 list of vertex positions of mesh +F : #F by 3 list of triangle indices into rows of V +detect_only : avoid constructing intersections results when possible {false} +first_only : return after detecting the first intersection (if + first_only==true, then detect_only should also be true) {false} +stitch_all : whether to stitch all resulting constructed elements into a + (non-manifold) mesh {false} +slow_and_more_precise_rounding : whether to use slow and more precise + rounding (see assign_scalar) {false} + + +Returns +------- +VV : #VV by 3 list of vertex positions +FF : #FF by 3 list of triangle indices into VV +IF : #intersecting face pairs by 2 list of intersecting face pairs, indexing F +J : #FF list of indices into F denoting birth triangle +IM : #VV list of indices into VV of unique vertices. + +See also +-------- +mesh_boolean +)igl_Qu8mg5v7"; + +npe_function(remesh_self_intersections) +npe_doc(ds_remesh_self_intersections) + +npe_arg(V, dense_float, dense_double) +npe_arg(F, dense_int, dense_long, dense_longlong) +npe_default_arg(detect_only, bool, false) +npe_default_arg(first_only, bool, false) +npe_default_arg(stitch_all, bool, false) +// Awaiting bump in libigl +//npe_default_arg(slow_and_more_precise_rounding, bool, false) + + +npe_begin_code() + Eigen::MatrixXd Vcpy = V.template cast(); + Eigen::MatrixXi Fcpy = F.template cast(); + + Eigen::MatrixXd VVcpy; + Eigen::MatrixXi FFcpy; + Eigen::VectorXi Jcpy; + Eigen::VectorXi IMcpy; + Eigen::MatrixXi IFcpy; + igl::copyleft::cgal::RemeshSelfIntersectionsParam params; + params.detect_only = detect_only; + params.first_only = first_only; + params.stitch_all = stitch_all; + //params.slow_and_more_precise_rounding = slow_and_more_precise_rounding; + igl::copyleft::cgal::remesh_self_intersections( + Vcpy,Fcpy,params,VVcpy,FFcpy,IFcpy,Jcpy,IMcpy); + + EigenDenseLike VV = VVcpy.cast(); + EigenDenseLike FF = FFcpy.cast(); + EigenDenseLike IF = IFcpy.cast(); + EigenDenseLike J = Jcpy.cast(); + EigenDenseLike IM = IMcpy.cast(); + return std::make_tuple(npe::move(VV), npe::move(FF), npe::move(IF), npe::move(J), npe::move(IM)); +npe_end_code() + From 4169d365a3e584bbd8886540068f2255a0e54662 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Fri, 1 Apr 2022 18:16:23 -0400 Subject: [PATCH 5/6] intersect_other --- src/copyleft/cgal/intersect_other.cpp | 87 +++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/copyleft/cgal/intersect_other.cpp diff --git a/src/copyleft/cgal/intersect_other.cpp b/src/copyleft/cgal/intersect_other.cpp new file mode 100644 index 00000000..d03e7fd0 --- /dev/null +++ b/src/copyleft/cgal/intersect_other.cpp @@ -0,0 +1,87 @@ +#include +#include +#include + + + +const char* ds_intersect_other = R"igl_Qu8mg5v7( +INTERSECT_OTHER Given a triangle mesh (VA,FA) and another mesh (VB,FB) find all +pairs of intersecting faces. Note that self-intersections are ignored. + + +Parameters +---------- +VA : #VA by 3 list of vertex positions of first mesh +FA : #FA by 3 list of triangle indices into VA +VB : #VB by 3 list of vertex positions of second mesh +FB : #FB by 3 list of triangle indices into VB +detect_only : avoid constructing intersections results when possible {false} +first_only : return after detecting the first intersection (if + first_only==true, then detect_only should also be true) {false} +stitch_all : whether to stitch all resulting constructed elements into a + (non-manifold) mesh {false} +slow_and_more_precise_rounding : whether to use slow and more precise + rounding (see assign_scalar) {false} + + +Returns +------- +IF : #intersecting face pairs by 2 list of intersecting face pairs, indexing F +VVAB : #VVAB by 3 list of vertex positions +FFAB : #FFAB by 3 list of triangle indices into VVAB +JAB : #FFAB list of indices into [FA;FB] denoting birth triangle +IMAB : #VVAB list of indices stitching duplicates (resulting from + mesh intersections) together + +See also +-------- +mesh_boolean +)igl_Qu8mg5v7"; + +npe_function(intersect_other) +npe_doc( ds_intersect_other) + +npe_arg(VA, dense_float, dense_double) +npe_arg(FA, dense_int, dense_long, dense_longlong) +npe_arg(VB, npe_matches(VA)) +npe_arg(FB, npe_matches(FA)) +npe_default_arg(detect_only, bool, false) +npe_default_arg(first_only, bool, false) +npe_default_arg(stitch_all, bool, false) +// Awaiting bump in libigl +//npe_default_arg(slow_and_more_precise_rounding, bool, false) + + +npe_begin_code() + Eigen::MatrixXd VAcpy = VA.template cast(); + Eigen::MatrixXi FAcpy = FA.template cast(); + Eigen::MatrixXd VBcpy = VB.template cast(); + Eigen::MatrixXi FBcpy = FB.template cast(); + + Eigen::MatrixXd VVABcpy; + Eigen::MatrixXi FFABcpy; + Eigen::VectorXi JABcpy; + Eigen::VectorXi IMABcpy; + Eigen::MatrixXi IFcpy; + igl::copyleft::cgal::RemeshSelfIntersectionsParam params; + params.detect_only = detect_only; + params.first_only = first_only; + params.stitch_all = stitch_all; + //params.slow_and_more_precise_rounding = slow_and_more_precise_rounding; + igl::copyleft::cgal::intersect_other( + VAcpy,FAcpy,VBcpy,FBcpy,params,IFcpy,VVABcpy,FFABcpy,JABcpy,IMABcpy); + + EigenDenseLike IF = IFcpy.cast(); + EigenDenseLike VVAB = VVABcpy.cast(); + EigenDenseLike FFAB = FFABcpy.cast(); + EigenDenseLike JAB = JABcpy.cast(); + EigenDenseLike IMAB = IMABcpy.cast(); + return std::make_tuple( + npe::move(IF), + npe::move(VVAB), + npe::move(FFAB), + npe::move( JAB), + npe::move(IMAB)); +npe_end_code() + + From 6aeec3058c3423bb5b42009ec76be074f5a54493 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Fri, 1 Apr 2022 19:22:21 -0400 Subject: [PATCH 6/6] bump libigl --- cmake/PyiglDependencies.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/PyiglDependencies.cmake b/cmake/PyiglDependencies.cmake index 4bc8018b..e9b1701b 100644 --- a/cmake/PyiglDependencies.cmake +++ b/cmake/PyiglDependencies.cmake @@ -20,7 +20,7 @@ include(FetchContent) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git - GIT_TAG 4fa1e22c8f30536477d20013f555dec2d7dba119 + GIT_TAG 1c3d487d8e77f816934374c271ef978807003405 ) FetchContent_MakeAvailable(libigl)