diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 7fdbaaba..6d7f58c0 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -50,8 +50,6 @@ jobs: env: CIBW_BUILD_VERBOSITY: 3 # This is very dubious... It *may* work because these are just cpp libraries that should not depend on the python version. Still, super-dubious. - CIBW_BEFORE_BUILD_WINDOWS: "python -m pip install delvewheel" - CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "python -m delvewheel repair --no-mangle-all --add-path build\\temp.win-amd64-3.6\\Release;build\\temp.win-amd64-3.6\\Release\\Release;build\\temp.win-amd64-3.6\\Release\\_deps\\gmp-src\\lib;build\\temp.win-amd64-3.6\\Release\\_deps\\mpfr-src\\lib -w {dest_dir} {wheel} " CIBW_TEST_REQUIRES: "gitpython" CIBW_TEST_COMMAND: "python {project}/tests/test_basic.py" CIBW_BUILD: "${{ matrix.cpversion }}-${{ matrix.os.cibw-arch }}" diff --git a/CMakeLists.txt b/CMakeLists.txt index 094afe21..1cdffc69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,15 @@ cmake_minimum_required(VERSION 3.16.0) project(pyigl) -if (NOT DEFINED ${PYLIBIGL_EXTERNAL}) +if (NOT DEFINED PYLIBIGL_EXTERNAL) set(PYLIBIGL_EXTERNAL ${CMAKE_CURRENT_SOURCE_DIR}/external) endif() +message(STATUS "PYIGL_OUTPUT_DIRECTORY: ${PYIGL_OUTPUT_DIRECTORY}") +if (NOT DEFINED PYIGL_OUTPUT_DIRECTORY) + message(FATAL_ERROR "PYIGL_OUTPUT_DIRECTORY must be defined externally") +endif() + list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) # Color output include(UseColors) @@ -18,23 +23,71 @@ include(CXXFeatures) # Generate position independent code by default set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE INTERNAL "") +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() # 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") +set_target_properties(pyigl PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${PYIGL_OUTPUT_DIRECTORY}" + RUNTIME_OUTPUT_DIRECTORY "${PYIGL_OUTPUT_DIRECTORY}" + LIBRARY_OUTPUT_DIRECTORY_RELEASE "${PYIGL_OUTPUT_DIRECTORY}" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${PYIGL_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 "${PYIGL_OUTPUT_DIRECTORY}/${subpath}") + file(MAKE_DIRECTORY ${output_dir}) + file(WRITE "${output_dir}/__init__.py" "from .${target_name} import *") + # https://stackoverflow.com/a/56514534/148668 + set_target_properties(${target_name} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${output_dir}" + RUNTIME_OUTPUT_DIRECTORY "${output_dir}" + LIBRARY_OUTPUT_DIRECTORY_RELEASE "${output_dir}" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${output_dir}") + # why do this? + target_link_libraries( pyigl INTERFACE ${target_name}) + endif() + # https://stackoverflow.com/a/69736197/148668 + if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + add_custom_command(TARGET ${target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ $ + COMMAND_EXPAND_LISTS) + endif() +endfunction() + +pyigl_include("copyleft" "cgal") + add_library(pyigl_classes MODULE classes/classes.cpp) target_link_libraries(pyigl_classes PRIVATE npe igl::core) @@ -42,6 +95,12 @@ target_link_libraries(pyigl_classes PRIVATE pybind11::module) set_target_properties(pyigl_classes PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" SUFFIX "${PYTHON_MODULE_EXTENSION}") target_include_directories(pyigl_classes PRIVATE "src/include") target_include_directories(pyigl_classes PRIVATE "${PYLIBIGL_EXTERNAL}/numpyeigen/src") +set_target_properties(pyigl_classes PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${PYIGL_OUTPUT_DIRECTORY}" + RUNTIME_OUTPUT_DIRECTORY "${PYIGL_OUTPUT_DIRECTORY}" + LIBRARY_OUTPUT_DIRECTORY_RELEASE "${PYIGL_OUTPUT_DIRECTORY}" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${PYIGL_OUTPUT_DIRECTORY}" + ) # Sort projects inside the solution set_property(GLOBAL PROPERTY USE_FOLDERS ON) diff --git a/cmake/PyiglDependencies.cmake b/cmake/PyiglDependencies.cmake index 22a62f34..d01f663f 100644 --- a/cmake/PyiglDependencies.cmake +++ b/cmake/PyiglDependencies.cmake @@ -19,7 +19,7 @@ include(FetchContent) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git - GIT_TAG 4a91b88f81aa094d076ad4f1460ae663295ac518 + GIT_TAG 78015d4da1c6799dfe15659ed35adfb3a5f23ffa ) FetchContent_GetProperties(libigl) FetchContent_MakeAvailable(libigl) diff --git a/igl/_version.py b/igl/_version.py index 3daaeb6c..8ef6ae96 100644 --- a/igl/_version.py +++ b/igl/_version.py @@ -1 +1 @@ -__version__ = "2.5.0dev" +__version__ = "2.5.2dev" 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/setup.py b/setup.py index c50b6e7e..8b2884da 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ def run(self): def build_extension(self, ext): extdir = os.path.join(os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))),"igl") - cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, + cmake_args = ['-DPYIGL_OUTPUT_DIRECTORY=' + extdir, '-DPYTHON_EXECUTABLE=' + sys.executable] @@ -47,7 +47,6 @@ def build_extension(self, ext): # cmake_args += ['-DDEBUG_TRACE=ON'] if platform.system() == "Windows": - cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)] cmake_generator = os.environ.get('CMAKE_GENERATOR', '') if cmake_generator != "NMake Makefiles" and "Ninja" not in cmake_generator: if sys.maxsize > 2**32: diff --git a/src/copyleft/cgal/convex_hull.cpp b/src/copyleft/cgal/convex_hull.cpp new file mode 100644 index 00000000..8e225e95 --- /dev/null +++ b/src/copyleft/cgal/convex_hull.cpp @@ -0,0 +1,31 @@ +#include +#include + +#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 +------- +F #F by 3 list of triangle indices into V +)igl_Qu8mg5v7"; + +npe_function(convex_hull) +npe_doc(ds_convex_hull) + +npe_arg(v, dense_float, dense_double) +npe_begin_code() + + EigenDenseInt 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() 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() + + 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() + 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() +