diff --git a/CMakeLists.txt b/CMakeLists.txt index 47e4feb2..4f10f3a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ include(CXXFeatures) set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE INTERNAL "") option(LIBIGL_COPYLEFT_CGAL "Build target igl_copyleft::cgal" ON) +option(LIBIGL_COPYLEFT_TETGEN "Build target igl_copyleft::tetgen" ON) option(LIBIGL_RESTRICTED_TRIANGLE "Build target igl_restricted::triangle" ON) # libigl options must come before include(PyiglDependencies) include(PyiglDependencies) @@ -90,6 +91,7 @@ function(pyigl_include prefix name) endfunction() pyigl_include("copyleft" "cgal") +pyigl_include("copyleft" "tetgen") pyigl_include("restricted" "triangle") diff --git a/cmake/PyiglDependencies.cmake b/cmake/PyiglDependencies.cmake index 401edd15..ca69a535 100644 --- a/cmake/PyiglDependencies.cmake +++ b/cmake/PyiglDependencies.cmake @@ -19,15 +19,15 @@ include(FetchContent) FetchContent_Declare( libigl GIT_REPOSITORY https://github.com/libigl/libigl.git - GIT_TAG 5779714a5bdd62e105a96edfa73d0d3755e33bc8 + GIT_TAG 1b07ebcf9e18c33ba342fd5c945dedbda055df78 ) FetchContent_GetProperties(libigl) FetchContent_MakeAvailable(libigl) FetchContent_Declare( numpyeigen - GIT_REPOSITORY https://github.com/alecjacobson/numpyeigen.git - GIT_TAG ef46595a8bf72f96301b4e92334b433fb1bd6c9e) + GIT_REPOSITORY https://github.com/fwilliams/numpyeigen.git + GIT_TAG 14acc7a71285979016ef39041d8cd4df97e4e829) # NumpyEigen's CMakeLists sets NPE_PYTHON_EXECUTABLE without a way to override, # so we must include directly rather that using FetchContent_MakeAvailable #FetchContent_MakeAvailable(numpyeigen) diff --git a/src/copyleft/tetgen/tetrahedralize.cpp b/src/copyleft/tetgen/tetrahedralize.cpp new file mode 100644 index 00000000..ad0c139f --- /dev/null +++ b/src/copyleft/tetgen/tetrahedralize.cpp @@ -0,0 +1,69 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2023 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include + + +#include +#include +#include + +#include + +const char* ds_tetrahedralize = R"igl_Qu8mg5v7( +)igl_Qu8mg5v7"; + +npe_function(tetrahedralize) +npe_doc(ds_tetrahedralize) + +npe_arg(V, dense_float, dense_double) +npe_default_arg(F, dense_int32, dense_int64, Eigen::MatrixXi(0,3)) +npe_default_arg(H, npe_matches(V) ,pybind11::array()) +npe_default_arg(switches, std::string, "Q") +npe_default_arg(return_adjacency_info, bool, false) +npe_default_arg(VM, npe_matches(F), pybind11::array()) +npe_default_arg(FM, npe_matches(F), pybind11::array()) +npe_default_arg( R, npe_matches(V), pybind11::array()) +npe_begin_code() +const bool has_markers = VM.size() > 0 || FM.size() > 0; +const bool has_regions = R.size() > 0; +EigenDenseLike TV; +EigenDenseLike TT, TF, TN, FT; +Eigen::Matrix TR,PT,TM; + +int num_regions; +igl::copyleft::tetgen::tetrahedralize( + V,F,H, + Eigen::Map>(VM.data(),VM.size()), + Eigen::Map>(FM.data(),FM.size()), + R,switches,TV,TT,TF,TM,TR,TN,PT,FT,num_regions); + +auto ret = std::list({npe::move(TV), npe::move(TT), npe::move(TF)}); + +if(has_markers) +{ + ret.push_back(npe::move(TM)); +} +if(has_regions) +{ + ret.push_back(npe::move(TR)); +} +if(return_adjacency_info) +{ + ret.push_back(npe::move(TN)); + ret.push_back(npe::move(PT)); + ret.push_back(npe::move(FT)); +} +if(has_regions) +{ + ret.push_back(pybind11::cast(num_regions)); +} + +return ret; + +npe_end_code() + diff --git a/src/writeMESH.cpp b/src/writeMESH.cpp new file mode 100644 index 00000000..9cf73ec4 --- /dev/null +++ b/src/writeMESH.cpp @@ -0,0 +1,44 @@ +// This file is part of libigl, a simple c++ geometry processing library. +// +// Copyright (C) 2023 Alec Jacobson +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at http://mozilla.org/MPL/2.0/. +#include +#include +#include +#include + +const char* ds_writeMESH = R"igl_Qu8mg5v7( + /// save a tetrahedral volume mesh to a .mesh file + /// + /// @tparam Scalar type for positions and vectors (will be cast as double) + /// @tparam Index type for indices (will be cast to int) + /// @param[in] mesh_file_name path of .mesh file + /// @param[in] V double matrix of vertex positions #V by 3 + /// @param[in] T #T list of tet indices into vertex positions + /// @param[in] F #F list of face indices into vertex positions + /// @return true on success, false on errors + /// + /// \bug Holes and regions are not supported +)igl_Qu8mg5v7"; + +npe_function(writeMESH) +npe_doc(ds_writeMESH) +npe_arg(filename, std::string) +npe_arg(V, dense_double, dense_float) +npe_arg(T, dense_int32, dense_int64) +npe_default_arg(F, npe_matches(T), pybind11::array()) + +npe_begin_code() + + assert_valid_tet_or_tri_mesh(V,T); + return igl::writeMESH(filename, V, T, F); + +npe_end_code() + + + + + diff --git a/tests/test_basic.py b/tests/test_basic.py index 2284bf69..a9645b59 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -5,6 +5,7 @@ import igl print("Using igl found at: ",igl.__file__) import igl.triangle +import igl.copyleft.tetgen import igl.copyleft.cgal import numpy as np import scipy as sp @@ -2535,6 +2536,26 @@ def test_triangulate(self): self.assertTrue(E2.shape == E.shape) self.assertTrue(EM2.shape == EM.shape) + def test_tetrahedralize(self): + V = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]],dtype='float64') + F = np.array([[0,1,2],[0,1,3],[0,2,3],[1,2,3]]) + TV,TT,TF = igl.copyleft.tetgen.tetrahedralize(V,F); + self.assertTrue(TV.shape == V.shape) + self.assertTrue(TT.shape == (4,)) + self.assertTrue(TF.shape == F.shape) + V = np.array([[1,1,0],[1,-1,0],[-1,-1,0],[-1,1,0],[0,0,1]],dtype='float64') + TV,TT,TF = igl.copyleft.tetgen.tetrahedralize(V,switches="cQ"); + self.assertTrue(TV.shape == V.shape) + self.assertTrue(TT.shape == (2,4)) + self.assertTrue(TF.shape == (6,3)) + TV,TT,TF = igl.copyleft.tetgen.tetrahedralize(self.v1,self.f1,switches="pQq1.34"); + igl.writeMESH("test.mesh",TV,TT,TF) + self.assertTrue(TV.shape[0] > self.v1.shape[0]) + self.assertTrue(TT.shape[0] > self.f1.shape[0]) + + + def test_writeMESH(self): + igl.writeMESH("test.mesh",self.v4,self.t4,self.f4) # copyleft.cgal def test_convex_hull(self): @@ -2599,6 +2620,10 @@ def test_triangle(self): # check that type is self.assertTrue(type(igl.copyleft) == type(igl)) + def test_copyleft_tetgen(self): + # check that type is + self.assertTrue(type(igl.copyleft.tetgen) == type(igl)) + if __name__ == '__main__':