├── .github └── workflows │ └── continuous.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── LICENSES_bundled ├── README.md ├── cmake ├── CXXFeatures.cmake ├── DownloadProject.CMakeLists.cmake.in ├── DownloadProject.cmake ├── PrependCurrentPath.cmake ├── UseColors.cmake ├── Warnings.cmake ├── WildMeshingDependencies.cmake └── WildMeshingDownloadExternal.cmake ├── environment.yml ├── setup.py ├── src ├── Utils.cpp ├── Utils.hpp ├── binding.cpp ├── tetrahedralize.cpp ├── tetrahedralize.hpp ├── triangulate.cpp ├── triangulate.hpp ├── triangulate_data.cpp └── triangulate_data.hpp ├── test ├── __init__.py ├── small_tet_test.py ├── tet_test.py └── tri_test.py └── wildmeshing ├── __init__.py ├── runners.py └── triangulate_svg.py /.github/workflows/continuous.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | env: 12 | CTEST_OUTPUT_ON_FAILURE: ON 13 | CTEST_PARALLEL_LEVEL: 2 14 | 15 | defaults: 16 | run: 17 | shell: bash -el {0} 18 | 19 | jobs: 20 | Build: 21 | name: ${{ matrix.name }} (${{ matrix.config }}) 22 | runs-on: ${{ matrix.os }} 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | os: [ubuntu-22.04, macOS-13, windows-2019] 27 | config: [Debug, Release] 28 | include: 29 | - os: ubuntu-22.04 30 | name: Linux 31 | - os: windows-2019 32 | name: Windows 33 | - os: macos-13 34 | name: macOS 35 | steps: 36 | - name: Checkout repository 37 | uses: actions/checkout@v4 38 | with: 39 | fetch-depth: 10 40 | submodules: "recursive" 41 | 42 | - name: Setup NMake (Windows) 43 | if: runner.os == 'Windows' 44 | uses: ilammy/msvc-dev-cmd@v1 45 | 46 | - name: Setup Conda 47 | uses: conda-incubator/setup-miniconda@v3 48 | with: 49 | channels: conda-forge 50 | python-version: 3.7 51 | channel-priority: true 52 | activate-environment: test-env 53 | 54 | - name: Install Dependencies 55 | run: conda install numpy svgwrite cmake git -y 56 | 57 | - name: Install Dependencies (Windows) 58 | if: runner.os == 'Windows' 59 | run: conda install mpir -y 60 | 61 | - name: Envs (Windows) 62 | if: runner.os == 'Windows' 63 | run: echo "CMAKE_GENERATOR=NMake Makefiles" >> $GITHUB_ENV 64 | 65 | - name: Configure git 66 | run: git config --global http.version HTTP/1.1 67 | 68 | - name: Build (Debug) 69 | if: matrix.config == 'Debug' 70 | run: | 71 | python setup.py build --debug install 72 | 73 | - name: Build (Release) 74 | if: matrix.config == 'Release' 75 | run: python setup.py build install 76 | 77 | - name: Fast Tests 78 | run: | 79 | python test/tri_test.py 80 | python test/small_tet_test.py 81 | 82 | - name: Slow Tests 83 | if: matrix.config == 'Release' 84 | run: | 85 | python test/tet_test.py 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 3rdparty 2 | build 3 | 4 | .vscode 5 | *.egg-info 6 | __pycache__ 7 | *.code-workspace 8 | *.so 9 | *.msh* 10 | *.stl 11 | dist 12 | wildmeshing/__init__.pyc 13 | *.html -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "wildmeshing/parse_svg"] 2 | path = wildmeshing/parse_svg 3 | url = https://github.com/teseoch/svgpathtools.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | cmake_minimum_required(VERSION 3.1) 3 | project(WildMeshing) 4 | ################################################################################ 5 | 6 | 7 | set(THIRD_PARTY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/) 8 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 9 | 10 | # option(FLOAT_TETWILD_WITH_EXACT_ENVELOPE "Use exact envelope" ON) 11 | option(LIBIGL_WITH_PREDICATES "Use exact predicates" ON) 12 | 13 | 14 | # Color output 15 | include(UseColors) 16 | 17 | # Prepend function 18 | include(PrependCurrentPath) 19 | 20 | 21 | # Extra warnings 22 | include(Warnings) 23 | 24 | # Use C++11/14 25 | include(CXXFeatures) 26 | 27 | # Sort projects inside the solution 28 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 29 | 30 | # Generate position independent code by default 31 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 32 | 33 | ################################################################################ 34 | 35 | 36 | # Setup dependencies 37 | include(WildMeshingDependencies) 38 | 39 | # triwild 40 | wildmeshing_download_triwild() 41 | add_subdirectory(${THIRD_PARTY_DIR}/triwild) 42 | 43 | 44 | # tetwild 45 | wildmeshing_download_tetwild() 46 | add_subdirectory(${THIRD_PARTY_DIR}/tetwild) 47 | 48 | 49 | # pybind11 50 | wildmeshing_download_pybind11() 51 | add_subdirectory(${THIRD_PARTY_DIR}/pybind11) 52 | 53 | 54 | #for testing purpose 55 | wildmeshing_download_data() 56 | 57 | 58 | ################################################################################ 59 | # Subdirectories 60 | ################################################################################ 61 | add_library(wildmeshing MODULE 62 | src/binding.cpp 63 | src/Utils.cpp src/Utils.hpp 64 | src/triangulate_data.hpp src/triangulate_data.cpp 65 | src/triangulate.cpp src/triangulate.hpp 66 | src/tetrahedralize.cpp src/tetrahedralize.hpp) 67 | target_include_directories(wildmeshing PRIVATE ${THIRD_PARTY_DIR}/triwild/src) 68 | target_compile_features(wildmeshing PUBLIC cxx_std_14) 69 | 70 | 71 | target_link_libraries(wildmeshing PRIVATE triwild_lib) 72 | target_link_libraries(wildmeshing PRIVATE FloatTetwild) 73 | target_link_libraries(wildmeshing PRIVATE pybind11::module) 74 | set_target_properties(wildmeshing PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" SUFFIX "${PYTHON_MODULE_EXTENSION}") 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Wildmeshing repository and source distributions bundle a number of libraries that 2 | are compatibly licensed. We list these here. The main license is at the end 3 | 4 | Name: triwild 5 | Files: https://github.com/wildmeshing/TriWild/blob/master/LICENSE.MPL2 6 | License: MPL2 7 | 8 | Name: tetwild 9 | License: MPL2 10 | 11 | Name: pybind11 12 | Files: https://github.com/pybind/pybind11/blob/master/LICENSE 13 | License: BSD 14 | 15 | Name: aabbcc 16 | Files: https://github.com/Yixin-Hu/aabbcc/blob/master/LICENSE 17 | License: zlib 18 | 19 | Name: cli11 20 | Files: https://github.com/CLIUtils/CLI11/blob/master/LICENSE 21 | License: 3-Clause BSD 22 | 23 | Name: geogram 24 | Files: http://alice.loria.fr/software/geogram/doc/html/geogram_license.html 25 | License: 3-clause BSD 26 | 27 | Name: libigl 28 | Files: https://github.com/libigl/libigl/blob/master/LICENSE.GPL 29 | License: MPL2 30 | 31 | Name: nlopt 32 | Files: https://nlopt.readthedocs.io/en/latest/NLopt_License_and_Copyright/ 33 | License: MIT (we use only Sbplx) 34 | 35 | Name: spdlog 36 | Files: https://github.com/gabime/spdlog/blob/v1.x/LICENSE 37 | License: MIT 38 | 39 | Name: tbb 40 | Files: https://github.com/wjakob/tbb/blob/master/LICENSE 41 | License: Apache-2.0 42 | 43 | Name: stdint 44 | Files: https://github.com/alicevision/geogram/blob/0ac4a5889f8eaef9372b888126deec2334128158/src/lib/geogram/third_party/pstdint.h 45 | License: BSD 46 | 47 | Name: LM6 48 | Files: https://github.com/alicevision/geogram/blob/0ac4a5889f8eaef9372b888126deec2334128158/src/lib/geogram/third_party/LM7/copyright.txt 49 | License: LGPL 50 | 51 | Name: rply 52 | Files: https://github.com/alicevision/geogram/blob/0ac4a5889f8eaef9372b888126deec2334128158/src/lib/geogram/third_party/rply/LICENSE 53 | License: MIT 54 | 55 | Name: zlib 56 | Files: https://github.com/alicevision/geogram/blob/0ac4a5889f8eaef9372b888126deec2334128158/src/lib/geogram/third_party/zlib/LICENSE 57 | License: MIT 58 | 59 | Name: gzstream 60 | Files: https://github.com/alicevision/geogram/blob/0ac4a5889f8eaef9372b888126deec2334128158/src/lib/geogram/third_party/gzstream/COPYING.LIB 61 | License: LGPL 62 | 63 | Name: Screened Poisson Surface Reconstruction 64 | Files: https://github.com/alicevision/geogram/blob/0ac4a5889f8eaef9372b888126deec2334128158/src/lib/geogram/third_party/PoissonRecon/LICENSE.txt 65 | License: MIT 66 | 67 | 68 | 69 | Mozilla Public License Version 2.0 70 | ================================== 71 | 72 | 1. Definitions 73 | -------------- 74 | 75 | 1.1. "Contributor" 76 | means each individual or legal entity that creates, contributes to 77 | the creation of, or owns Covered Software. 78 | 79 | 1.2. "Contributor Version" 80 | means the combination of the Contributions of others (if any) used 81 | by a Contributor and that particular Contributor's Contribution. 82 | 83 | 1.3. "Contribution" 84 | means Covered Software of a particular Contributor. 85 | 86 | 1.4. "Covered Software" 87 | means Source Code Form to which the initial Contributor has attached 88 | the notice in Exhibit A, the Executable Form of such Source Code 89 | Form, and Modifications of such Source Code Form, in each case 90 | including portions thereof. 91 | 92 | 1.5. "Incompatible With Secondary Licenses" 93 | means 94 | 95 | (a) that the initial Contributor has attached the notice described 96 | in Exhibit B to the Covered Software; or 97 | 98 | (b) that the Covered Software was made available under the terms of 99 | version 1.1 or earlier of the License, but not also under the 100 | terms of a Secondary License. 101 | 102 | 1.6. "Executable Form" 103 | means any form of the work other than Source Code Form. 104 | 105 | 1.7. "Larger Work" 106 | means a work that combines Covered Software with other material, in 107 | a separate file or files, that is not Covered Software. 108 | 109 | 1.8. "License" 110 | means this document. 111 | 112 | 1.9. "Licensable" 113 | means having the right to grant, to the maximum extent possible, 114 | whether at the time of the initial grant or subsequently, any and 115 | all of the rights conveyed by this License. 116 | 117 | 1.10. "Modifications" 118 | means any of the following: 119 | 120 | (a) any file in Source Code Form that results from an addition to, 121 | deletion from, or modification of the contents of Covered 122 | Software; or 123 | 124 | (b) any new file in Source Code Form that contains any Covered 125 | Software. 126 | 127 | 1.11. "Patent Claims" of a Contributor 128 | means any patent claim(s), including without limitation, method, 129 | process, and apparatus claims, in any patent Licensable by such 130 | Contributor that would be infringed, but for the grant of the 131 | License, by the making, using, selling, offering for sale, having 132 | made, import, or transfer of either its Contributions or its 133 | Contributor Version. 134 | 135 | 1.12. "Secondary License" 136 | means either the GNU General Public License, Version 2.0, the GNU 137 | Lesser General Public License, Version 2.1, the GNU Affero General 138 | Public License, Version 3.0, or any later versions of those 139 | licenses. 140 | 141 | 1.13. "Source Code Form" 142 | means the form of the work preferred for making modifications. 143 | 144 | 1.14. "You" (or "Your") 145 | means an individual or a legal entity exercising rights under this 146 | License. For legal entities, "You" includes any entity that 147 | controls, is controlled by, or is under common control with You. For 148 | purposes of this definition, "control" means (a) the power, direct 149 | or indirect, to cause the direction or management of such entity, 150 | whether by contract or otherwise, or (b) ownership of more than 151 | fifty percent (50%) of the outstanding shares or beneficial 152 | ownership of such entity. 153 | 154 | 2. License Grants and Conditions 155 | -------------------------------- 156 | 157 | 2.1. Grants 158 | 159 | Each Contributor hereby grants You a world-wide, royalty-free, 160 | non-exclusive license: 161 | 162 | (a) under intellectual property rights (other than patent or trademark) 163 | Licensable by such Contributor to use, reproduce, make available, 164 | modify, display, perform, distribute, and otherwise exploit its 165 | Contributions, either on an unmodified basis, with Modifications, or 166 | as part of a Larger Work; and 167 | 168 | (b) under Patent Claims of such Contributor to make, use, sell, offer 169 | for sale, have made, import, and otherwise transfer either its 170 | Contributions or its Contributor Version. 171 | 172 | 2.2. Effective Date 173 | 174 | The licenses granted in Section 2.1 with respect to any Contribution 175 | become effective for each Contribution on the date the Contributor first 176 | distributes such Contribution. 177 | 178 | 2.3. Limitations on Grant Scope 179 | 180 | The licenses granted in this Section 2 are the only rights granted under 181 | this License. No additional rights or licenses will be implied from the 182 | distribution or licensing of Covered Software under this License. 183 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 184 | Contributor: 185 | 186 | (a) for any code that a Contributor has removed from Covered Software; 187 | or 188 | 189 | (b) for infringements caused by: (i) Your and any other third party's 190 | modifications of Covered Software, or (ii) the combination of its 191 | Contributions with other software (except as part of its Contributor 192 | Version); or 193 | 194 | (c) under Patent Claims infringed by Covered Software in the absence of 195 | its Contributions. 196 | 197 | This License does not grant any rights in the trademarks, service marks, 198 | or logos of any Contributor (except as may be necessary to comply with 199 | the notice requirements in Section 3.4). 200 | 201 | 2.4. Subsequent Licenses 202 | 203 | No Contributor makes additional grants as a result of Your choice to 204 | distribute the Covered Software under a subsequent version of this 205 | License (see Section 10.2) or under the terms of a Secondary License (if 206 | permitted under the terms of Section 3.3). 207 | 208 | 2.5. Representation 209 | 210 | Each Contributor represents that the Contributor believes its 211 | Contributions are its original creation(s) or it has sufficient rights 212 | to grant the rights to its Contributions conveyed by this License. 213 | 214 | 2.6. Fair Use 215 | 216 | This License is not intended to limit any rights You have under 217 | applicable copyright doctrines of fair use, fair dealing, or other 218 | equivalents. 219 | 220 | 2.7. Conditions 221 | 222 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 223 | in Section 2.1. 224 | 225 | 3. Responsibilities 226 | ------------------- 227 | 228 | 3.1. Distribution of Source Form 229 | 230 | All distribution of Covered Software in Source Code Form, including any 231 | Modifications that You create or to which You contribute, must be under 232 | the terms of this License. You must inform recipients that the Source 233 | Code Form of the Covered Software is governed by the terms of this 234 | License, and how they can obtain a copy of this License. You may not 235 | attempt to alter or restrict the recipients' rights in the Source Code 236 | Form. 237 | 238 | 3.2. Distribution of Executable Form 239 | 240 | If You distribute Covered Software in Executable Form then: 241 | 242 | (a) such Covered Software must also be made available in Source Code 243 | Form, as described in Section 3.1, and You must inform recipients of 244 | the Executable Form how they can obtain a copy of such Source Code 245 | Form by reasonable means in a timely manner, at a charge no more 246 | than the cost of distribution to the recipient; and 247 | 248 | (b) You may distribute such Executable Form under the terms of this 249 | License, or sublicense it under different terms, provided that the 250 | license for the Executable Form does not attempt to limit or alter 251 | the recipients' rights in the Source Code Form under this License. 252 | 253 | 3.3. Distribution of a Larger Work 254 | 255 | You may create and distribute a Larger Work under terms of Your choice, 256 | provided that You also comply with the requirements of this License for 257 | the Covered Software. If the Larger Work is a combination of Covered 258 | Software with a work governed by one or more Secondary Licenses, and the 259 | Covered Software is not Incompatible With Secondary Licenses, this 260 | License permits You to additionally distribute such Covered Software 261 | under the terms of such Secondary License(s), so that the recipient of 262 | the Larger Work may, at their option, further distribute the Covered 263 | Software under the terms of either this License or such Secondary 264 | License(s). 265 | 266 | 3.4. Notices 267 | 268 | You may not remove or alter the substance of any license notices 269 | (including copyright notices, patent notices, disclaimers of warranty, 270 | or limitations of liability) contained within the Source Code Form of 271 | the Covered Software, except that You may alter any license notices to 272 | the extent required to remedy known factual inaccuracies. 273 | 274 | 3.5. Application of Additional Terms 275 | 276 | You may choose to offer, and to charge a fee for, warranty, support, 277 | indemnity or liability obligations to one or more recipients of Covered 278 | Software. However, You may do so only on Your own behalf, and not on 279 | behalf of any Contributor. You must make it absolutely clear that any 280 | such warranty, support, indemnity, or liability obligation is offered by 281 | You alone, and You hereby agree to indemnify every Contributor for any 282 | liability incurred by such Contributor as a result of warranty, support, 283 | indemnity or liability terms You offer. You may include additional 284 | disclaimers of warranty and limitations of liability specific to any 285 | jurisdiction. 286 | 287 | 4. Inability to Comply Due to Statute or Regulation 288 | --------------------------------------------------- 289 | 290 | If it is impossible for You to comply with any of the terms of this 291 | License with respect to some or all of the Covered Software due to 292 | statute, judicial order, or regulation then You must: (a) comply with 293 | the terms of this License to the maximum extent possible; and (b) 294 | describe the limitations and the code they affect. Such description must 295 | be placed in a text file included with all distributions of the Covered 296 | Software under this License. Except to the extent prohibited by statute 297 | or regulation, such description must be sufficiently detailed for a 298 | recipient of ordinary skill to be able to understand it. 299 | 300 | 5. Termination 301 | -------------- 302 | 303 | 5.1. The rights granted under this License will terminate automatically 304 | if You fail to comply with any of its terms. However, if You become 305 | compliant, then the rights granted under this License from a particular 306 | Contributor are reinstated (a) provisionally, unless and until such 307 | Contributor explicitly and finally terminates Your grants, and (b) on an 308 | ongoing basis, if such Contributor fails to notify You of the 309 | non-compliance by some reasonable means prior to 60 days after You have 310 | come back into compliance. Moreover, Your grants from a particular 311 | Contributor are reinstated on an ongoing basis if such Contributor 312 | notifies You of the non-compliance by some reasonable means, this is the 313 | first time You have received notice of non-compliance with this License 314 | from such Contributor, and You become compliant prior to 30 days after 315 | Your receipt of the notice. 316 | 317 | 5.2. If You initiate litigation against any entity by asserting a patent 318 | infringement claim (excluding declaratory judgment actions, 319 | counter-claims, and cross-claims) alleging that a Contributor Version 320 | directly or indirectly infringes any patent, then the rights granted to 321 | You by any and all Contributors for the Covered Software under Section 322 | 2.1 of this License shall terminate. 323 | 324 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 325 | end user license agreements (excluding distributors and resellers) which 326 | have been validly granted by You or Your distributors under this License 327 | prior to termination shall survive termination. 328 | 329 | ************************************************************************ 330 | * * 331 | * 6. Disclaimer of Warranty * 332 | * ------------------------- * 333 | * * 334 | * Covered Software is provided under this License on an "as is" * 335 | * basis, without warranty of any kind, either expressed, implied, or * 336 | * statutory, including, without limitation, warranties that the * 337 | * Covered Software is free of defects, merchantable, fit for a * 338 | * particular purpose or non-infringing. The entire risk as to the * 339 | * quality and performance of the Covered Software is with You. * 340 | * Should any Covered Software prove defective in any respect, You * 341 | * (not any Contributor) assume the cost of any necessary servicing, * 342 | * repair, or correction. This disclaimer of warranty constitutes an * 343 | * essential part of this License. No use of any Covered Software is * 344 | * authorized under this License except under this disclaimer. * 345 | * * 346 | ************************************************************************ 347 | 348 | ************************************************************************ 349 | * * 350 | * 7. Limitation of Liability * 351 | * -------------------------- * 352 | * * 353 | * Under no circumstances and under no legal theory, whether tort * 354 | * (including negligence), contract, or otherwise, shall any * 355 | * Contributor, or anyone who distributes Covered Software as * 356 | * permitted above, be liable to You for any direct, indirect, * 357 | * special, incidental, or consequential damages of any character * 358 | * including, without limitation, damages for lost profits, loss of * 359 | * goodwill, work stoppage, computer failure or malfunction, or any * 360 | * and all other commercial damages or losses, even if such party * 361 | * shall have been informed of the possibility of such damages. This * 362 | * limitation of liability shall not apply to liability for death or * 363 | * personal injury resulting from such party's negligence to the * 364 | * extent applicable law prohibits such limitation. Some * 365 | * jurisdictions do not allow the exclusion or limitation of * 366 | * incidental or consequential damages, so this exclusion and * 367 | * limitation may not apply to You. * 368 | * * 369 | ************************************************************************ 370 | 371 | 8. Litigation 372 | ------------- 373 | 374 | Any litigation relating to this License may be brought only in the 375 | courts of a jurisdiction where the defendant maintains its principal 376 | place of business and such litigation shall be governed by laws of that 377 | jurisdiction, without reference to its conflict-of-law provisions. 378 | Nothing in this Section shall prevent a party's ability to bring 379 | cross-claims or counter-claims. 380 | 381 | 9. Miscellaneous 382 | ---------------- 383 | 384 | This License represents the complete agreement concerning the subject 385 | matter hereof. If any provision of this License is held to be 386 | unenforceable, such provision shall be reformed only to the extent 387 | necessary to make it enforceable. Any law or regulation which provides 388 | that the language of a contract shall be construed against the drafter 389 | shall not be used to construe this License against a Contributor. 390 | 391 | 10. Versions of the License 392 | --------------------------- 393 | 394 | 10.1. New Versions 395 | 396 | Mozilla Foundation is the license steward. Except as provided in Section 397 | 10.3, no one other than the license steward has the right to modify or 398 | publish new versions of this License. Each version will be given a 399 | distinguishing version number. 400 | 401 | 10.2. Effect of New Versions 402 | 403 | You may distribute the Covered Software under the terms of the version 404 | of the License under which You originally received the Covered Software, 405 | or under the terms of any subsequent version published by the license 406 | steward. 407 | 408 | 10.3. Modified Versions 409 | 410 | If you create software not governed by this License, and you want to 411 | create a new license for such software, you may create and use a 412 | modified version of this License if you rename the license and remove 413 | any references to the name of the license steward (except to note that 414 | such modified license differs from this License). 415 | 416 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 417 | Licenses 418 | 419 | If You choose to distribute Source Code Form that is Incompatible With 420 | Secondary Licenses under the terms of this version of the License, the 421 | notice described in Exhibit B of this License must be attached. 422 | 423 | Exhibit A - Source Code Form License Notice 424 | ------------------------------------------- 425 | 426 | This Source Code Form is subject to the terms of the Mozilla Public 427 | License, v. 2.0. If a copy of the MPL was not distributed with this 428 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 429 | 430 | If it is not possible or desirable to put the notice in a particular 431 | file, then You may include the notice in a location (such as a LICENSE 432 | file in a relevant directory) where a recipient would be likely to look 433 | for such a notice. 434 | 435 | You may add additional accurate notices of copyright ownership. 436 | 437 | Exhibit B - "Incompatible With Secondary Licenses" Notice 438 | --------------------------------------------------------- 439 | 440 | This Source Code Form is "Incompatible With Secondary Licenses", as 441 | defined by the Mozilla Public License, v. 2.0. 442 | -------------------------------------------------------------------------------- /LICENSES_bundled: -------------------------------------------------------------------------------- 1 | The Wildmeshing repository and source distributions bundle a number of libraries that 2 | are compatibly licensed. We list these here. 3 | 4 | Name: triwild 5 | Files: https://github.com/wildmeshing/TriWild/blob/master/LICENSE.MPL2 6 | License: MPL2 7 | 8 | Name: tetwild 9 | License: MPL2 10 | 11 | Name: pybind11 12 | Files: https://github.com/pybind/pybind11/blob/master/LICENSE 13 | License: BSD 14 | 15 | Name: aabbcc 16 | Files: https://github.com/Yixin-Hu/aabbcc/blob/master/LICENSE 17 | License: zlib 18 | 19 | Name: cli11 20 | Files: https://github.com/CLIUtils/CLI11/blob/master/LICENSE 21 | License: 3-Clause BSD 22 | 23 | Name: geogram 24 | Files: http://alice.loria.fr/software/geogram/doc/html/geogram_license.html 25 | License: 3-clause BSD 26 | 27 | Name: libigl 28 | Files: https://github.com/libigl/libigl/blob/master/LICENSE.GPL 29 | License: MPL2 30 | 31 | Name: nlopt 32 | Files: https://nlopt.readthedocs.io/en/latest/NLopt_License_and_Copyright/ 33 | License: MIT (we use only Sbplx) 34 | 35 | Name: spdlog 36 | Files: https://github.com/gabime/spdlog/blob/v1.x/LICENSE 37 | License: MIT 38 | 39 | Name: tbb 40 | Files: https://github.com/wjakob/tbb/blob/master/LICENSE 41 | License: Apache-2.0 42 | 43 | Name: stdint 44 | Files: https://github.com/alicevision/geogram/blob/0ac4a5889f8eaef9372b888126deec2334128158/src/lib/geogram/third_party/pstdint.h 45 | License: BSD 46 | 47 | Name: LM6 48 | Files: https://github.com/alicevision/geogram/blob/0ac4a5889f8eaef9372b888126deec2334128158/src/lib/geogram/third_party/LM7/copyright.txt 49 | License: LGPL 50 | 51 | Name: rply 52 | Files: https://github.com/alicevision/geogram/blob/0ac4a5889f8eaef9372b888126deec2334128158/src/lib/geogram/third_party/rply/LICENSE 53 | License: MIT 54 | 55 | Name: zlib 56 | Files: https://github.com/alicevision/geogram/blob/0ac4a5889f8eaef9372b888126deec2334128158/src/lib/geogram/third_party/zlib/LICENSE 57 | License: MIT 58 | 59 | Name: gzstream 60 | Files: https://github.com/alicevision/geogram/blob/0ac4a5889f8eaef9372b888126deec2334128158/src/lib/geogram/third_party/gzstream/COPYING.LIB 61 | License: LGPL 62 | 63 | Name: Screened Poisson Surface Reconstruction 64 | Files: https://github.com/alicevision/geogram/blob/0ac4a5889f8eaef9372b888126deec2334128158/src/lib/geogram/third_party/PoissonRecon/LICENSE.txt 65 | License: MIT 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wildmeshing python bindings 2 | ![Build](https://github.com/wildmeshing/wildmeshing-python/workflows/Build/badge.svg) 3 | 4 | python interface to **triangulate** and **tetrahedralize** 5 | 6 | use: 7 | 8 | ```bash 9 | python setup.py develop 10 | ``` 11 | 12 | or 13 | ```bash 14 | python -m pip install . -vv 15 | ``` 16 | to install 17 | -------------------------------------------------------------------------------- /cmake/CXXFeatures.cmake: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | 3 | if(NOT (${CMAKE_VERSION} VERSION_LESS "3.8.0")) 4 | # For CMake 3.8 and above, we can use meta features directly provided by CMake itself 5 | set(CXX11_FEATURES cxx_std_11) 6 | set(CXX14_FEATURES cxx_std_14) 7 | set(CXX17_FEATURES cxx_std_17) 8 | return() 9 | endif() 10 | 11 | ################################################################################ 12 | 13 | set(CXX11_FEATURES 14 | cxx_auto_type 15 | cxx_constexpr 16 | ) 17 | 18 | set(CXX14_FEATURES 19 | cxx_generic_lambdas 20 | ) 21 | 22 | set(CXX17_FEATURES 23 | cxx_generic_lambdas 24 | ) 25 | 26 | ################################################################################ 27 | 28 | # https://cmake.org/cmake/help/v3.1/prop_gbl/CMAKE_CXX_KNOWN_FEATURES.html 29 | # cxx_aggregate_default_initializers Aggregate default initializers, as defined in N3605. 30 | # cxx_alias_templates Template aliases, as defined in N2258. 31 | # cxx_alignas Alignment control alignas, as defined in N2341. 32 | # cxx_alignof Alignment control alignof, as defined in N2341. 33 | # cxx_attributes Generic attributes, as defined in N2761. 34 | # cxx_attribute_deprecated deprecated]] attribute, as defined in N3760. 35 | # cxx_auto_type Automatic type deduction, as defined in N1984. 36 | # cxx_binary_literals Binary literals, as defined in N3472. 37 | # cxx_constexpr Constant expressions, as defined in N2235. 38 | # cxx_contextual_conversions Contextual conversions, as defined in N3323. 39 | # cxx_decltype_incomplete_return_types Decltype on incomplete return types, as defined in N3276. 40 | # cxx_decltype Decltype, as defined in N2343. 41 | # cxx_decltype_auto decltype(auto) semantics, as defined in N3638. 42 | # cxx_default_function_template_args Default template arguments for function templates, as defined in DR226 43 | # cxx_defaulted_functions Defaulted functions, as defined in N2346. 44 | # cxx_defaulted_move_initializers Defaulted move initializers, as defined in N3053. 45 | # cxx_delegating_constructors Delegating constructors, as defined in N1986. 46 | # cxx_deleted_functions Deleted functions, as defined in N2346. 47 | # cxx_digit_separators Digit separators, as defined in N3781. 48 | # cxx_enum_forward_declarations Enum forward declarations, as defined in N2764. 49 | # cxx_explicit_conversions Explicit conversion operators, as defined in N2437. 50 | # cxx_extended_friend_declarations Extended friend declarations, as defined in N1791. 51 | # cxx_extern_templates Extern templates, as defined in N1987. 52 | # cxx_final Override control final keyword, as defined in N2928, N3206 and N3272. 53 | # cxx_func_identifier Predefined __func__ identifier, as defined in N2340. 54 | # cxx_generalized_initializers Initializer lists, as defined in N2672. 55 | # cxx_generic_lambdas Generic lambdas, as defined in N3649. 56 | # cxx_inheriting_constructors Inheriting constructors, as defined in N2540. 57 | # cxx_inline_namespaces Inline namespaces, as defined in N2535. 58 | # cxx_lambdas Lambda functions, as defined in N2927. 59 | # cxx_lambda_init_captures Initialized lambda captures, as defined in N3648. 60 | # cxx_local_type_template_args Local and unnamed types as template arguments, as defined in N2657. 61 | # cxx_long_long_type long long type, as defined in N1811. 62 | # cxx_noexcept Exception specifications, as defined in N3050. 63 | # cxx_nonstatic_member_init Non-static data member initialization, as defined in N2756. 64 | # cxx_nullptr Null pointer, as defined in N2431. 65 | # cxx_override Override control override keyword, as defined in N2928, N3206 and N3272. 66 | # cxx_range_for Range-based for, as defined in N2930. 67 | # cxx_raw_string_literals Raw string literals, as defined in N2442. 68 | # cxx_reference_qualified_functions Reference qualified functions, as defined in N2439. 69 | # cxx_relaxed_constexpr Relaxed constexpr, as defined in N3652. 70 | # cxx_return_type_deduction Return type deduction on normal functions, as defined in N3386. 71 | # cxx_right_angle_brackets Right angle bracket parsing, as defined in N1757. 72 | # cxx_rvalue_references R-value references, as defined in N2118. 73 | # cxx_sizeof_member Size of non-static data members, as defined in N2253. 74 | # cxx_static_assert Static assert, as defined in N1720. 75 | # cxx_strong_enums Strongly typed enums, as defined in N2347. 76 | # cxx_thread_local Thread-local variables, as defined in N2659. 77 | # cxx_trailing_return_types Automatic function return type, as defined in N2541. 78 | # cxx_unicode_literals Unicode string literals, as defined in N2442. 79 | # cxx_uniform_initialization Uniform intialization, as defined in N2640. 80 | # cxx_unrestricted_unions Unrestricted unions, as defined in N2544. 81 | # cxx_user_literals User-defined literals, as defined in N2765. 82 | # cxx_variable_templates Variable templates, as defined in N3651. 83 | # cxx_variadic_macros Variadic macros, as defined in N1653. 84 | # cxx_variadic_templates Variadic templates, as defined in N2242. 85 | # cxx_template_template_parameters Template template parameters, as defined in ISO/IEC 14882:1998. 86 | -------------------------------------------------------------------------------- /cmake/DownloadProject.CMakeLists.cmake.in: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved MIT License. See accompanying 2 | # file LICENSE or https://github.com/Crascit/DownloadProject for details. 3 | 4 | cmake_minimum_required(VERSION 2.8.2) 5 | 6 | project(${DL_ARGS_PROJ}-download NONE) 7 | 8 | include(ExternalProject) 9 | ExternalProject_Add(${DL_ARGS_PROJ}-download 10 | ${DL_ARGS_UNPARSED_ARGUMENTS} 11 | SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" 12 | BINARY_DIR "${DL_ARGS_BINARY_DIR}" 13 | CONFIGURE_COMMAND "" 14 | BUILD_COMMAND "" 15 | INSTALL_COMMAND "" 16 | TEST_COMMAND "" 17 | ) 18 | -------------------------------------------------------------------------------- /cmake/DownloadProject.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved MIT License. See accompanying 2 | # file LICENSE or https://github.com/Crascit/DownloadProject for details. 3 | # 4 | # MODULE: DownloadProject 5 | # 6 | # PROVIDES: 7 | # download_project( PROJ projectName 8 | # [PREFIX prefixDir] 9 | # [DOWNLOAD_DIR downloadDir] 10 | # [SOURCE_DIR srcDir] 11 | # [BINARY_DIR binDir] 12 | # [QUIET] 13 | # ... 14 | # ) 15 | # 16 | # Provides the ability to download and unpack a tarball, zip file, git repository, 17 | # etc. at configure time (i.e. when the cmake command is run). How the downloaded 18 | # and unpacked contents are used is up to the caller, but the motivating case is 19 | # to download source code which can then be included directly in the build with 20 | # add_subdirectory() after the call to download_project(). Source and build 21 | # directories are set up with this in mind. 22 | # 23 | # The PROJ argument is required. The projectName value will be used to construct 24 | # the following variables upon exit (obviously replace projectName with its actual 25 | # value): 26 | # 27 | # projectName_SOURCE_DIR 28 | # projectName_BINARY_DIR 29 | # 30 | # The SOURCE_DIR and BINARY_DIR arguments are optional and would not typically 31 | # need to be provided. They can be specified if you want the downloaded source 32 | # and build directories to be located in a specific place. The contents of 33 | # projectName_SOURCE_DIR and projectName_BINARY_DIR will be populated with the 34 | # locations used whether you provide SOURCE_DIR/BINARY_DIR or not. 35 | # 36 | # The DOWNLOAD_DIR argument does not normally need to be set. It controls the 37 | # location of the temporary CMake build used to perform the download. 38 | # 39 | # The PREFIX argument can be provided to change the base location of the default 40 | # values of DOWNLOAD_DIR, SOURCE_DIR and BINARY_DIR. If all of those three arguments 41 | # are provided, then PREFIX will have no effect. The default value for PREFIX is 42 | # CMAKE_BINARY_DIR. 43 | # 44 | # The QUIET option can be given if you do not want to show the output associated 45 | # with downloading the specified project. 46 | # 47 | # In addition to the above, any other options are passed through unmodified to 48 | # ExternalProject_Add() to perform the actual download, patch and update steps. 49 | # The following ExternalProject_Add() options are explicitly prohibited (they 50 | # are reserved for use by the download_project() command): 51 | # 52 | # CONFIGURE_COMMAND 53 | # BUILD_COMMAND 54 | # INSTALL_COMMAND 55 | # TEST_COMMAND 56 | # 57 | # Only those ExternalProject_Add() arguments which relate to downloading, patching 58 | # and updating of the project sources are intended to be used. Also note that at 59 | # least one set of download-related arguments are required. 60 | # 61 | # If using CMake 3.2 or later, the UPDATE_DISCONNECTED option can be used to 62 | # prevent a check at the remote end for changes every time CMake is run 63 | # after the first successful download. See the documentation of the ExternalProject 64 | # module for more information. It is likely you will want to use this option if it 65 | # is available to you. Note, however, that the ExternalProject implementation contains 66 | # bugs which result in incorrect handling of the UPDATE_DISCONNECTED option when 67 | # using the URL download method or when specifying a SOURCE_DIR with no download 68 | # method. Fixes for these have been created, the last of which is scheduled for 69 | # inclusion in CMake 3.8.0. Details can be found here: 70 | # 71 | # https://gitlab.kitware.com/cmake/cmake/commit/bdca68388bd57f8302d3c1d83d691034b7ffa70c 72 | # https://gitlab.kitware.com/cmake/cmake/issues/16428 73 | # 74 | # If you experience build errors related to the update step, consider avoiding 75 | # the use of UPDATE_DISCONNECTED. 76 | # 77 | # EXAMPLE USAGE: 78 | # 79 | # include(DownloadProject) 80 | # download_project(PROJ googletest 81 | # GIT_REPOSITORY https://github.com/google/googletest.git 82 | # GIT_TAG master 83 | # UPDATE_DISCONNECTED 1 84 | # QUIET 85 | # ) 86 | # 87 | # add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) 88 | # 89 | #======================================================================================== 90 | 91 | 92 | set(_DownloadProjectDir "${CMAKE_CURRENT_LIST_DIR}") 93 | 94 | include(CMakeParseArguments) 95 | 96 | function(download_project) 97 | 98 | set(options QUIET) 99 | set(oneValueArgs 100 | PROJ 101 | PREFIX 102 | DOWNLOAD_DIR 103 | SOURCE_DIR 104 | BINARY_DIR 105 | # Prevent the following from being passed through 106 | CONFIGURE_COMMAND 107 | BUILD_COMMAND 108 | INSTALL_COMMAND 109 | TEST_COMMAND 110 | ) 111 | set(multiValueArgs "") 112 | 113 | cmake_parse_arguments(DL_ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 114 | 115 | # Hide output if requested 116 | if (DL_ARGS_QUIET) 117 | set(OUTPUT_QUIET "OUTPUT_QUIET") 118 | else() 119 | unset(OUTPUT_QUIET) 120 | message(STATUS "Downloading/updating ${DL_ARGS_PROJ}") 121 | endif() 122 | 123 | # Set up where we will put our temporary CMakeLists.txt file and also 124 | # the base point below which the default source and binary dirs will be. 125 | # The prefix must always be an absolute path. 126 | if (NOT DL_ARGS_PREFIX) 127 | set(DL_ARGS_PREFIX "${CMAKE_BINARY_DIR}") 128 | else() 129 | get_filename_component(DL_ARGS_PREFIX "${DL_ARGS_PREFIX}" ABSOLUTE 130 | BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") 131 | endif() 132 | if (NOT DL_ARGS_DOWNLOAD_DIR) 133 | set(DL_ARGS_DOWNLOAD_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-download") 134 | endif() 135 | 136 | # Ensure the caller can know where to find the source and build directories 137 | if (NOT DL_ARGS_SOURCE_DIR) 138 | set(DL_ARGS_SOURCE_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-src") 139 | endif() 140 | if (NOT DL_ARGS_BINARY_DIR) 141 | set(DL_ARGS_BINARY_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-build") 142 | endif() 143 | set(${DL_ARGS_PROJ}_SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" PARENT_SCOPE) 144 | set(${DL_ARGS_PROJ}_BINARY_DIR "${DL_ARGS_BINARY_DIR}" PARENT_SCOPE) 145 | 146 | # The way that CLion manages multiple configurations, it causes a copy of 147 | # the CMakeCache.txt to be copied across due to it not expecting there to 148 | # be a project within a project. This causes the hard-coded paths in the 149 | # cache to be copied and builds to fail. To mitigate this, we simply 150 | # remove the cache if it exists before we configure the new project. It 151 | # is safe to do so because it will be re-generated. Since this is only 152 | # executed at the configure step, it should not cause additional builds or 153 | # downloads. 154 | file(REMOVE "${DL_ARGS_DOWNLOAD_DIR}/CMakeCache.txt") 155 | 156 | # Create and build a separate CMake project to carry out the download. 157 | # If we've already previously done these steps, they will not cause 158 | # anything to be updated, so extra rebuilds of the project won't occur. 159 | # Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project 160 | # has this set to something not findable on the PATH. 161 | configure_file("${_DownloadProjectDir}/DownloadProject.CMakeLists.cmake.in" 162 | "${DL_ARGS_DOWNLOAD_DIR}/CMakeLists.txt") 163 | execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" 164 | -D "CMAKE_MAKE_PROGRAM:FILE=${CMAKE_MAKE_PROGRAM}" 165 | . 166 | RESULT_VARIABLE result 167 | ${OUTPUT_QUIET} 168 | WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" 169 | ) 170 | if(result) 171 | message(FATAL_ERROR "CMake step for ${DL_ARGS_PROJ} failed: ${result}") 172 | endif() 173 | execute_process(COMMAND ${CMAKE_COMMAND} --build . 174 | RESULT_VARIABLE result 175 | ${OUTPUT_QUIET} 176 | WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" 177 | ) 178 | if(result) 179 | message(FATAL_ERROR "Build step for ${DL_ARGS_PROJ} failed: ${result}") 180 | endif() 181 | 182 | endfunction() 183 | -------------------------------------------------------------------------------- /cmake/PrependCurrentPath.cmake: -------------------------------------------------------------------------------- 1 | function(prepend_current_path SOURCE_FILES) 2 | # Use recursive substitution to expand SOURCE_FILES 3 | unset(MODIFIED) 4 | foreach(SOURCE_FILE IN ITEMS ${${SOURCE_FILES}}) 5 | list(APPEND MODIFIED "${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE_FILE}") 6 | endforeach() 7 | set(${SOURCE_FILES} ${MODIFIED} PARENT_SCOPE) 8 | endfunction() 9 | -------------------------------------------------------------------------------- /cmake/UseColors.cmake: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # When using Clang, there is nothing to do: colors are enabled by default 3 | # When using GCC >= 4.9, colored diagnostics can be enabled natively 4 | # When using an older version, one can use gccfilter (a perl script) 5 | # 6 | # I do not recommend using gccfilter as of now (May 2014), because it seems to 7 | # be bugged. But if you still want to try, here is how to install it on Ubuntu: 8 | # 9 | # 10 | # 1) Download the perl script and add it to you $PATH 11 | # mkdir -p ~/.local/bin 12 | # wget -P ~/.local/bin http://www.mixtion.org/gccfilter/gccfilter 13 | # chmod +x ~/local/bin/gccfilter 14 | # echo 'PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc 15 | # 16 | # 2) Install the dependencies 17 | # * Term::ANSIColor 18 | # sudo cpan 19 | # cpan> install Term::ANSIColor 20 | # * The module "Getopt::Long" is included in "perl-base" 21 | # * For Getopt::ArgvFile and Regexp::Common ... 22 | # sudo apt-get install libgetopt-argvfile-perl libregexp-common-perl 23 | # 24 | ################################################################################ 25 | 26 | if(CMAKE_COMPILER_IS_GNUCXX) 27 | # If GCC >= 4.9, just activate the right option 28 | # We enable colorized diagnostics always instead of using "auto" so that 29 | # they're still colored when using Ninja. 30 | if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) 31 | message(STATUS "GCC >= 4.9 detected, enabling colored diagnostics") 32 | add_definitions(-fdiagnostics-color=always) 33 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always") 34 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdiagnostics-color=always") 35 | return() 36 | endif() 37 | # If GCC < 4.9, maybe we can use gccfilter 38 | find_program(GCC_FILTER gccfilter) 39 | if(GCC_FILTER) 40 | option(COLOR_GCC "Use GCCFilter to color compiler output messages" OFF) 41 | set(COLOR_GCC_OPTIONS "-c -r -w" CACHE STRING "Arguments that are passed to gccfilter when output coloring is switchend on. Defaults to -c -r -w.") 42 | if(COLOR_GCC) 43 | set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${GCC_FILTER} ${COLOR_GCC_OPTIONS}") 44 | message(STATUS "Using gccfilter for colored diagnostics") 45 | endif() 46 | endif() 47 | endif() 48 | -------------------------------------------------------------------------------- /cmake/Warnings.cmake: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | cmake_minimum_required(VERSION 2.6.3) 3 | ################################################################################ 4 | # See comments and discussions here: 5 | # http://stackoverflow.com/questions/5088460/flags-to-enable-thorough-and-verbose-g-warnings 6 | ################################################################################ 7 | 8 | if(TARGET warnings::all) 9 | return() 10 | endif() 11 | 12 | set(MY_FLAGS 13 | -Wall 14 | -Wextra 15 | -pedantic 16 | 17 | # -Wconversion 18 | #-Wunsafe-loop-optimizations # broken with C++11 loops 19 | -Wunused 20 | 21 | -Wno-long-long 22 | -Wpointer-arith 23 | -Wformat=2 24 | -Wuninitialized 25 | -Wcast-qual 26 | -Wmissing-noreturn 27 | -Wmissing-format-attribute 28 | -Wredundant-decls 29 | 30 | -Werror=implicit 31 | -Werror=nonnull 32 | -Werror=init-self 33 | -Werror=main 34 | -Werror=missing-braces 35 | -Werror=sequence-point 36 | -Werror=return-type 37 | -Werror=trigraphs 38 | -Werror=array-bounds 39 | -Werror=write-strings 40 | -Werror=address 41 | -Werror=int-to-pointer-cast 42 | -Werror=pointer-to-int-cast 43 | 44 | -Wno-unused-variable 45 | -Wunused-but-set-variable 46 | -Wno-unused-parameter 47 | 48 | #-Weffc++ 49 | -Wno-old-style-cast 50 | # -Wno-sign-conversion 51 | #-Wsign-conversion 52 | 53 | -Wshadow 54 | 55 | -Wstrict-null-sentinel 56 | -Woverloaded-virtual 57 | -Wsign-promo 58 | -Wstack-protector 59 | -Wstrict-aliasing 60 | -Wstrict-aliasing=2 61 | -Wswitch-default 62 | -Wswitch-enum 63 | -Wswitch-unreachable 64 | 65 | -Wcast-align 66 | -Wdisabled-optimization 67 | #-Winline # produces warning on default implicit destructor 68 | -Winvalid-pch 69 | # -Wmissing-include-dirs 70 | -Wpacked 71 | -Wno-padded 72 | -Wstrict-overflow 73 | -Wstrict-overflow=2 74 | 75 | -Wctor-dtor-privacy 76 | -Wlogical-op 77 | -Wnoexcept 78 | -Woverloaded-virtual 79 | # -Wundef 80 | 81 | -Wnon-virtual-dtor 82 | -Wdelete-non-virtual-dtor 83 | -Werror=non-virtual-dtor 84 | -Werror=delete-non-virtual-dtor 85 | 86 | -Wno-sign-compare 87 | 88 | ########### 89 | # GCC 6.1 # 90 | ########### 91 | 92 | -Wnull-dereference 93 | -fdelete-null-pointer-checks 94 | -Wduplicated-cond 95 | -Wmisleading-indentation 96 | 97 | #-Weverything 98 | 99 | ########################### 100 | # Enabled by -Weverything # 101 | ########################### 102 | 103 | #-Wdocumentation 104 | #-Wdocumentation-unknown-command 105 | #-Wfloat-equal 106 | #-Wcovered-switch-default 107 | 108 | #-Wglobal-constructors 109 | #-Wexit-time-destructors 110 | #-Wmissing-variable-declarations 111 | #-Wextra-semi 112 | #-Wweak-vtables 113 | #-Wno-source-uses-openmp 114 | #-Wdeprecated 115 | #-Wnewline-eof 116 | #-Wmissing-prototypes 117 | 118 | #-Wno-c++98-compat 119 | #-Wno-c++98-compat-pedantic 120 | 121 | ########################### 122 | # Need to check if those are still valid today 123 | ########################### 124 | 125 | #-Wimplicit-atomic-properties 126 | #-Wmissing-declarations 127 | #-Wmissing-prototypes 128 | #-Wstrict-selector-match 129 | #-Wundeclared-selector 130 | #-Wunreachable-code 131 | 132 | # Not a warning, but enable link-time-optimization 133 | # TODO: Check out modern CMake version of setting this flag 134 | # https://cmake.org/cmake/help/latest/module/CheckIPOSupported.html 135 | #-flto 136 | 137 | # Gives meaningful stack traces 138 | -fno-omit-frame-pointer 139 | -fno-optimize-sibling-calls 140 | ) 141 | 142 | # Flags above don't make sense for MSVC 143 | if(MSVC) 144 | set(MY_FLAGS) 145 | endif() 146 | 147 | include(CheckCXXCompilerFlag) 148 | 149 | add_library(warnings_all INTERFACE) 150 | add_library(warnings::all ALIAS warnings_all) 151 | 152 | foreach(FLAG IN ITEMS ${MY_FLAGS}) 153 | string(REPLACE "=" "-" FLAG_VAR "${FLAG}") 154 | if(NOT DEFINED IS_SUPPORTED_${FLAG_VAR}) 155 | check_cxx_compiler_flag("${FLAG}" IS_SUPPORTED_${FLAG_VAR}) 156 | endif() 157 | if(IS_SUPPORTED_${FLAG_VAR}) 158 | target_compile_options(warnings_all INTERFACE ${FLAG}) 159 | endif() 160 | endforeach() 161 | -------------------------------------------------------------------------------- /cmake/WildMeshingDependencies.cmake: -------------------------------------------------------------------------------- 1 | # Prepare dependencies 2 | # 3 | # For each third-party library, if the appropriate target doesn't exist yet, 4 | # download it via external project, and add_subdirectory to build it alongside 5 | # this project. 6 | 7 | ### Configuration 8 | set(WILDMESHING_ROOT "${CMAKE_CURRENT_LIST_DIR}/..") 9 | set(WILDMESHING_EXTERNAL ${THIRD_PARTY_DIR}) 10 | 11 | # Download and update 3rdparty libraries 12 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) 13 | list(REMOVE_DUPLICATES CMAKE_MODULE_PATH) 14 | include(WildMeshingDownloadExternal) 15 | 16 | ################################################################################ 17 | # Required libraries 18 | ################################################################################ 19 | 20 | -------------------------------------------------------------------------------- /cmake/WildMeshingDownloadExternal.cmake: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | include(DownloadProject) 3 | 4 | # With CMake 3.8 and above, we can hide warnings about git being in a 5 | # detached head by passing an extra GIT_CONFIG option 6 | if(NOT (${CMAKE_VERSION} VERSION_LESS "3.8.0")) 7 | set(WILDMESHING_EXTRA_OPTIONS "GIT_CONFIG advice.detachedHead=false") 8 | else() 9 | set(WILDMESHING_EXTRA_OPTIONS "") 10 | endif() 11 | 12 | # Shortcut function 13 | function(wildmeshing_download_project name) 14 | download_project( 15 | PROJ ${name} 16 | SOURCE_DIR ${WILDMESHING_EXTERNAL}/${name} 17 | DOWNLOAD_DIR ${WILDMESHING_EXTERNAL}/.cache/${name} 18 | QUIET 19 | ${WILDMESHING_EXTRA_OPTIONS} 20 | ${ARGN} 21 | ) 22 | endfunction() 23 | 24 | ################################################################################ 25 | 26 | function(wildmeshing_download_triwild) 27 | wildmeshing_download_project(triwild 28 | GIT_REPOSITORY https://github.com/wildmeshing/TriWild 29 | GIT_TAG ac7977c1da9fb25de8c0f7c666055e6d09034686 30 | ) 31 | endfunction() 32 | 33 | function(wildmeshing_download_tetwild) 34 | wildmeshing_download_project(tetwild 35 | GIT_REPOSITORY https://github.com/wildmeshing/fTetWild 36 | GIT_TAG f471f09dd26006745387dd61694762f861c787b9 37 | ) 38 | endfunction() 39 | 40 | function(wildmeshing_download_pybind11) 41 | wildmeshing_download_project(pybind11 42 | GIT_REPOSITORY https://github.com/pybind/pybind11 43 | GIT_TAG a2e59f0e7065404b44dfe92a28aca47ba1378dc4 44 | ) 45 | endfunction() 46 | 47 | # data 48 | function(wildmeshing_download_data) 49 | wildmeshing_download_project(data 50 | GIT_REPOSITORY https://github.com/wildmeshing/data 51 | GIT_TAG 363f8e860673a4e4f68df6465b99e86809c96283 52 | ) 53 | endfunction() 54 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | channels: 2 | - conda-forge 3 | dependencies: 4 | - numpy 5 | - meshplot 6 | - wildmeshing -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | # import sysconfig 5 | import platform 6 | import subprocess 7 | 8 | from distutils.version import LooseVersion 9 | from setuptools import setup, Extension, find_packages 10 | from setuptools.command.build_ext import build_ext 11 | 12 | 13 | class CMakeExtension(Extension): 14 | def __init__(self, name, sourcedir=''): 15 | Extension.__init__(self, name, sources=[]) 16 | self.sourcedir = os.path.abspath(sourcedir) 17 | 18 | 19 | class CMakeBuild(build_ext): 20 | def run(self): 21 | try: 22 | out = subprocess.check_output(['cmake', '--version']) 23 | except OSError: 24 | raise RuntimeError( 25 | "CMake must be installed to build the following extensions: , ".join(e.name for e in self.extensions)) 26 | 27 | # self.debug = True 28 | 29 | cmake_version = LooseVersion( 30 | re.search(r'version\s*([\d.]+)', out.decode()).group(1)) 31 | if cmake_version < '3.1.0': 32 | raise RuntimeError("CMake >= 3.1.0 is required") 33 | 34 | for ext in self.extensions: 35 | self.build_extension(ext) 36 | 37 | def build_extension(self, ext): 38 | extdir = os.path.join(os.path.abspath(os.path.dirname( 39 | self.get_ext_fullpath(ext.name))), "wildmeshing") 40 | 41 | cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, 42 | '-DPYTHON_EXECUTABLE=' + sys.executable, 43 | ] 44 | 45 | cfg = 'Debug' if self.debug else 'Release' 46 | build_args = ['--config', cfg] 47 | cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] 48 | 49 | if platform.system() == "Windows": 50 | cmake_args += [ 51 | '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)] 52 | if os.environ.get('CMAKE_GENERATOR') != "NMake Makefiles": 53 | if sys.maxsize > 2**32: 54 | cmake_args += ['-A', 'x64'] 55 | build_args += ['--', '/m'] 56 | else: 57 | build_args += ['--', '-j2'] 58 | 59 | env = os.environ.copy() 60 | env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format( 61 | env.get('CXXFLAGS', ''), self.distribution.get_version()) 62 | 63 | if not os.path.exists(self.build_temp): 64 | os.makedirs(self.build_temp) 65 | subprocess.check_call(['cmake', ext.sourcedir] + 66 | cmake_args, cwd=self.build_temp, env=env) 67 | 68 | subprocess.check_call(['cmake', '--build', '.'] + 69 | build_args, cwd=self.build_temp) 70 | 71 | print() # Add an empty line for cleaner output 72 | 73 | 74 | with open("README.md", "r") as fh: 75 | long_description = fh.read() 76 | 77 | 78 | setup( 79 | name="wildmeshing", 80 | version="0.4", 81 | author="Teseo Schneider", 82 | author_email="", 83 | description="WildMeshing Bindings", 84 | long_description=long_description, 85 | long_description_content_type="text/markdown", 86 | url="todo", 87 | ext_modules=[CMakeExtension('wildmeshing')], 88 | cmdclass=dict(build_ext=CMakeBuild), 89 | packages=find_packages(), 90 | classifiers=[ 91 | "Programming Language :: Python :: 3", 92 | "License :: OSI Approved :: MPL-2.0 License" 93 | ], 94 | python_requires='>=3.6', 95 | install_requires=[ 96 | 'svgwrite', 97 | 'numpy', 98 | 'argparse'], 99 | entry_points={ 100 | 'console_scripts': [ 101 | 'wm_tetrahedralize = wildmeshing.runners:tetrahedralize', 102 | 'wm_triangulate = wildmeshing.runners:triangulate' 103 | ] 104 | }, 105 | test_suite="test" 106 | ) 107 | -------------------------------------------------------------------------------- /src/Utils.cpp: -------------------------------------------------------------------------------- 1 | #include "Utils.hpp" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace wildmeshing_binding 14 | { 15 | 16 | namespace 17 | { 18 | class GeoLoggerForward : public GEO::LoggerClient 19 | { 20 | std::shared_ptr logger_; 21 | 22 | public: 23 | template 24 | GeoLoggerForward(T logger) : logger_(logger) {} 25 | 26 | private: 27 | std::string truncate(const std::string &msg) 28 | { 29 | static size_t prefix_len = GEO::CmdLine::ui_feature(" ", false).size(); 30 | return msg.substr(prefix_len, msg.size() - 1 - prefix_len); 31 | } 32 | 33 | protected: 34 | void div(const std::string &title) override 35 | { 36 | logger_->trace(title.substr(0, title.size() - 1)); 37 | } 38 | 39 | void out(const std::string &str) override 40 | { 41 | logger_->info(truncate(str)); 42 | } 43 | 44 | void warn(const std::string &str) override 45 | { 46 | logger_->warn(truncate(str)); 47 | } 48 | 49 | void err(const std::string &str) override 50 | { 51 | logger_->error(truncate(str)); 52 | } 53 | 54 | void status(const std::string &str) override 55 | { 56 | // Errors and warnings are also dispatched as status by geogram, but without 57 | // the "feature" header. We thus forward them as trace, to avoid duplicated 58 | // logger info... 59 | logger_->trace(str.substr(0, str.size() - 1)); 60 | } 61 | }; 62 | } // namespace 63 | 64 | void init_globals() 65 | { 66 | static bool initialized = false; 67 | 68 | if (!initialized) 69 | { 70 | spdlog::init_thread_pool(8192, 1); 71 | 72 | #ifndef WIN32 73 | setenv("GEO_NO_SIGNAL_HANDLER", "1", 1); 74 | #endif 75 | 76 | GEO::initialize(); 77 | 78 | // Import standard command line arguments, and custom ones 79 | GEO::CmdLine::import_arg_group("standard"); 80 | GEO::CmdLine::import_arg_group("pre"); 81 | GEO::CmdLine::import_arg_group("algo"); 82 | 83 | // GEO::Logger *geo_logger = GEO::Logger::instance(); 84 | // geo_logger->unregister_all_clients(); 85 | // geo_logger->register_client(new GeoLoggerForward(floatTetWild::logger().clone("geogram"))); 86 | // geo_logger->set_pretty(false); 87 | 88 | initialized = true; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef WILDMESHING_SKIP_BINDINGS 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace py = pybind11; 12 | #endif 13 | namespace wildmeshing_binding 14 | { 15 | void init_globals(); 16 | } 17 | -------------------------------------------------------------------------------- /src/binding.cpp: -------------------------------------------------------------------------------- 1 | #include "Utils.hpp" 2 | 3 | #include "triangulate_data.hpp" 4 | #include "triangulate.hpp" 5 | #include "tetrahedralize.hpp" 6 | 7 | PYBIND11_MODULE(wildmeshing, m) 8 | { 9 | // m.doc() = "..." 10 | wildmeshing_binding::triangulate_data(m); 11 | wildmeshing_binding::triangulate(m); 12 | wildmeshing_binding::tetrahedralize(m); 13 | } -------------------------------------------------------------------------------- /src/tetrahedralize.cpp: -------------------------------------------------------------------------------- 1 | #include "tetrahedralize.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #ifdef FLOAT_TETWILD_USE_TBB 22 | #include 23 | #include 24 | #endif 25 | 26 | using namespace floatTetWild; 27 | using namespace Eigen; 28 | 29 | namespace wildmeshing_binding 30 | { 31 | Tetrahedralizer::Tetrahedralizer( 32 | double stop_quality, int max_its, int stage, int stop_p, 33 | double epsilon, double edge_length_r, 34 | bool skip_simplify, bool coarsen) : skip_simplify(skip_simplify) 35 | { 36 | wildmeshing_binding::init_globals(); 37 | 38 | Parameters ¶ms = mesh.params; 39 | 40 | // params.input_path = input; 41 | // params.output_path = output; 42 | 43 | params.stop_energy = stop_quality; 44 | params.max_its = max_its; 45 | params.stage = stage; 46 | params.stop_p = stop_p; 47 | 48 | params.coarsen = coarsen; 49 | 50 | params.eps_rel = epsilon; 51 | params.ideal_edge_length_rel = edge_length_r; 52 | 53 | params.log_level = 6; 54 | 55 | unsigned int max_threads = std::numeric_limits::max(); 56 | unsigned int num_threads = 1; 57 | #ifdef FLOAT_TETWILD_USE_TBB 58 | const size_t MB = 1024 * 1024; 59 | const size_t stack_size = 64 * MB; 60 | num_threads = std::max(1u, std::thread::hardware_concurrency()); 61 | num_threads = std::min(max_threads, num_threads); 62 | // params.num_threads = num_threads; 63 | std::cout << "TBB threads " << num_threads << std::endl; 64 | tbb::task_scheduler_init scheduler(num_threads, stack_size); 65 | #endif 66 | set_num_threads(num_threads); 67 | } 68 | 69 | void Tetrahedralizer::set_num_threads(int num_threads) 70 | { 71 | Parameters ¶ms = mesh.params; 72 | params.num_threads = num_threads; 73 | } 74 | 75 | void Tetrahedralizer::set_log_level(int level) 76 | { 77 | Parameters ¶ms = mesh.params; 78 | params.log_level = std::max(0, std::min(6, level)); 79 | spdlog::set_level(static_cast(params.log_level)); 80 | spdlog::flush_every(std::chrono::seconds(3)); 81 | } 82 | 83 | bool Tetrahedralizer::load_mesh(const std::string &path, const std::string &tag_path, const std::vector &epsr_tags) 84 | { 85 | Parameters ¶ms = mesh.params; 86 | params.input_path = path; 87 | params.tag_path = tag_path; 88 | 89 | if (!params.tag_path.empty()) 90 | { 91 | input_tags.reserve(input_faces.size()); 92 | std::string line; 93 | std::ifstream fin(params.tag_path); 94 | if (fin.is_open()) 95 | { 96 | while (getline(fin, line)) 97 | { 98 | input_tags.push_back(std::stoi(line)); 99 | } 100 | fin.close(); 101 | } 102 | else 103 | { 104 | throw std::invalid_argument("Invalid mesh format tag at " + params.tag_path); 105 | } 106 | } 107 | 108 | #ifdef NEW_ENVELOPE 109 | params.input_epsr_tags = epsr_tags; 110 | #endif 111 | 112 | #ifdef NEW_ENVELOPE 113 | if (!MeshIO::load_mesh(params.input_path, input_vertices, input_faces, sf_mesh, input_tags, params.input_epsr_tags)) 114 | #else 115 | if (!MeshIO::load_mesh(params.input_path, input_vertices, input_faces, sf_mesh, input_tags)) 116 | #endif 117 | { 118 | throw std::invalid_argument("Invalid mesh path at " + params.input_path); 119 | return false; 120 | } 121 | else if (input_vertices.empty() || input_faces.empty()) 122 | { 123 | throw std::invalid_argument("Invalid mesh path at " + params.input_path); 124 | return false; 125 | } 126 | 127 | #ifdef NEW_ENVELOPE 128 | if (!params.input_epsr_tags.empty()) 129 | { 130 | if (params.input_epsr_tags.size() != input_vertices.size()) 131 | { 132 | throw std::invalid_argument("epsr_tags need to be same size as vertices, " + std::to_string(params.input_epsr_tags.size()) + " vs " + std::to_string(input_vertices.size())); 133 | return false; 134 | } 135 | } 136 | #endif 137 | 138 | return load_mesh_aux(); 139 | } 140 | 141 | bool Tetrahedralizer::boolean_operation(const std::string &json_string) 142 | { 143 | json csg_tree; 144 | std::ifstream file(json_string); 145 | 146 | if (file.is_open()) 147 | { 148 | file >> csg_tree; 149 | file.close(); 150 | } 151 | else 152 | csg_tree = json::parse(json_string); 153 | 154 | std::vector meshes; 155 | 156 | CSGTreeParser::get_meshes(csg_tree, meshes, tree_with_ids); 157 | has_json_csg = true; 158 | 159 | bool ok = CSGTreeParser::load_and_merge(meshes, input_vertices, input_faces, sf_mesh, input_tags); 160 | if (!ok) 161 | { 162 | throw std::invalid_argument("Invalid mesh path in the json"); 163 | } 164 | 165 | load_mesh_aux(); 166 | return ok; 167 | } 168 | 169 | void Tetrahedralizer::set_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, const std::vector &epsr_tags) 170 | { 171 | if (F.cols() != 3) 172 | throw std::invalid_argument("Mesh format not supported, F should have 3 cols"); 173 | if (V.cols() != 3) 174 | throw std::invalid_argument("Mesh format not supported, V should have 3 cols"); 175 | 176 | input_vertices.resize(V.rows()); 177 | for (int i = 0; i < V.rows(); ++i) 178 | { 179 | input_vertices[i][0] = V(i, 0); 180 | input_vertices[i][1] = V(i, 1); 181 | input_vertices[i][2] = V(i, 2); 182 | } 183 | 184 | input_faces.resize(F.rows()); 185 | for (int i = 0; i < F.rows(); ++i) 186 | { 187 | input_faces[i][0] = F(i, 0); 188 | input_faces[i][1] = F(i, 1); 189 | input_faces[i][2] = F(i, 2); 190 | } 191 | 192 | Parameters ¶ms = mesh.params; 193 | 194 | #ifdef NEW_ENVELOPE 195 | params.input_epsr_tags = epsr_tags; 196 | 197 | if (!params.input_epsr_tags.empty()) 198 | { 199 | if (params.input_epsr_tags.size() != input_vertices.size()) 200 | { 201 | throw std::invalid_argument("epsr_tags need to be same size as vertices, " + std::to_string(params.input_epsr_tags.size()) + " vs " + std::to_string(input_vertices.size())); 202 | } 203 | } 204 | #endif 205 | 206 | #ifdef NEW_ENVELOPE 207 | MeshIO::load_mesh(input_vertices, input_faces, sf_mesh, input_tags, params.input_epsr_tags); 208 | #else 209 | MeshIO::load_mesh(input_vertices, input_faces, sf_mesh, input_tags); 210 | #endif 211 | 212 | load_mesh_aux(); 213 | } 214 | 215 | void Tetrahedralizer::set_meshes(const std::vector &V, const std::vector &F) 216 | { 217 | std::vector> vs(V.size()); 218 | std::vector> fs(F.size()); 219 | 220 | if (V.size() != F.size()) 221 | throw std::invalid_argument("V and F must have the same size"); 222 | 223 | for (int j = 0; j < V.size(); ++j) 224 | { 225 | if (V[j].cols() != 3) 226 | throw std::invalid_argument("Mesh format not supported, V should have 3 cols"); 227 | 228 | vs[j].resize(V[j].rows()); 229 | for (int i = 0; i < V[j].rows(); ++i) 230 | { 231 | vs[j][i][0] = V[j](i, 0); 232 | vs[j][i][1] = V[j](i, 1); 233 | vs[j][i][2] = V[j](i, 2); 234 | } 235 | } 236 | 237 | for (int j = 0; j < F.size(); ++j) 238 | { 239 | if (F[j].cols() != 3) 240 | throw std::invalid_argument("Mesh format not supported, F should have 3 cols"); 241 | 242 | fs[j].resize(F[j].rows()); 243 | for (int i = 0; i < F[j].rows(); ++i) 244 | { 245 | fs[j][i][0] = F[j](i, 0); 246 | fs[j][i][1] = F[j](i, 1); 247 | fs[j][i][2] = F[j](i, 2); 248 | } 249 | } 250 | 251 | CSGTreeParser::merge(vs, fs, input_vertices, input_faces, sf_mesh, input_tags); 252 | 253 | load_mesh_aux(); 254 | } 255 | 256 | void Tetrahedralizer::set_sizing_field(const std::string &path) 257 | { 258 | PyMesh::MshLoader mshLoader(path); 259 | Eigen::VectorXd V_in = mshLoader.get_nodes(); 260 | Eigen::VectorXi T_in = mshLoader.get_elements(); 261 | Eigen::VectorXd values = mshLoader.get_node_field("values"); 262 | 263 | set_sizing_field(V_in, T_in, values); 264 | } 265 | 266 | void Tetrahedralizer::set_sizing_field(const Eigen::MatrixXd &V, Eigen::MatrixXi &T, const Eigen::VectorXd &values) 267 | { 268 | assert(V.cols() == 3); 269 | assert(T.cols() == 4); 270 | assert(values.size() == V.rows()); 271 | 272 | if (V.cols() != 3) 273 | throw std::invalid_argument("V should have 3 cols"); 274 | if (T.cols() != 4) 275 | throw std::invalid_argument("T should have 3 cols"); 276 | if (values.size() != V.rows()) 277 | throw std::invalid_argument("values should have the same length as V.rows()"); 278 | 279 | Eigen::VectorXd V_in(V.size()); 280 | Eigen::VectorXi T_in(T.size()); 281 | 282 | int index = 0; 283 | for (int i = 0; i < V.rows(); ++i) 284 | { 285 | for (int j = 0; j < 3; ++j) 286 | V_in[index++] = V(i, j); 287 | } 288 | 289 | index = 0; 290 | for (int i = 0; i < T.rows(); ++i) 291 | { 292 | for (int j = 0; j < 4; ++j) 293 | T_in[index++] = T(i, j); 294 | } 295 | 296 | set_sizing_field(V_in, T_in, values); 297 | } 298 | 299 | void Tetrahedralizer::set_sizing_field(const Eigen::VectorXd &V_in, const Eigen::VectorXi &T_in, const Eigen::VectorXd &values) 300 | { 301 | Parameters ¶ms = mesh.params; 302 | params.apply_sizing_field = true; 303 | params.get_sizing_field_value = [V_in, T_in, values](const Vector3 &p) 304 | { 305 | GEO::Mesh bg_mesh; 306 | bg_mesh.vertices.clear(); 307 | bg_mesh.vertices.create_vertices((int)V_in.rows() / 3); 308 | for (int i = 0; i < V_in.rows() / 3; i++) 309 | { 310 | GEO::vec3 &p = bg_mesh.vertices.point(i); 311 | for (int j = 0; j < 3; j++) 312 | p[j] = V_in(i * 3 + j); 313 | } 314 | bg_mesh.cells.clear(); 315 | bg_mesh.cells.create_tets((int)T_in.rows() / 4); 316 | for (int i = 0; i < T_in.rows() / 4; i++) 317 | { 318 | for (int j = 0; j < 4; j++) 319 | bg_mesh.cells.set_vertex(i, j, T_in(i * 4 + j)); 320 | } 321 | 322 | GEO::MeshCellsAABB bg_aabb(bg_mesh, false); 323 | GEO::vec3 geo_p(p[0], p[1], p[2]); 324 | int bg_t_id = bg_aabb.containing_tet(geo_p); 325 | if (bg_t_id == GEO::MeshCellsAABB::NO_TET) 326 | return -1.; 327 | 328 | // compute barycenter 329 | std::array vs; 330 | for (int j = 0; j < 4; j++) 331 | { 332 | vs[j] = Vector3(V_in(T_in(bg_t_id * 4 + j) * 3), V_in(T_in(bg_t_id * 4 + j) * 3 + 1), 333 | V_in(T_in(bg_t_id * 4 + j) * 3 + 2)); 334 | } 335 | double value = 0; 336 | for (int j = 0; j < 4; j++) 337 | { 338 | Vector3 n = ((vs[(j + 1) % 4] - vs[j]).cross(vs[(j + 2) % 4] - vs[j])).normalized(); 339 | double d = (vs[(j + 3) % 4] - vs[j]).dot(n); 340 | if (d == 0) 341 | continue; 342 | double weight = abs((p - vs[j]).dot(n) / d); 343 | value += weight * values(T_in(bg_t_id * 4 + (j + 3) % 4)); 344 | } 345 | return value; // / mesh.params.ideal_edge_length; 346 | }; 347 | } 348 | 349 | void Tetrahedralizer::set_sizing_field(std::function &field) 350 | { 351 | Parameters ¶ms = mesh.params; 352 | params.apply_sizing_field = true; 353 | params.get_sizing_field_value = field; 354 | } 355 | 356 | bool Tetrahedralizer::load_mesh_aux() 357 | { 358 | Parameters ¶ms = mesh.params; 359 | 360 | if (input_tags.size() != input_faces.size()) 361 | { 362 | input_tags.resize(input_faces.size()); 363 | std::fill(input_tags.begin(), input_tags.end(), 0); 364 | } 365 | tree = std::make_unique(sf_mesh); 366 | 367 | if (!params.init(tree->get_sf_diag())) 368 | { 369 | throw std::invalid_argument("Unable to initialize the tree, probably a problem with the mesh"); 370 | return false; 371 | } 372 | 373 | #ifdef NEW_ENVELOPE 374 | if (!params.input_epsr_tags.empty()) 375 | tree->init_sf_tree(input_vertices, input_faces, params.input_epsr_tags, params.bbox_diag_length); 376 | else 377 | tree->init_sf_tree(input_vertices, input_faces, params.eps); 378 | #endif 379 | 380 | stats().record(StateInfo::init_id, 0, input_vertices.size(), input_faces.size(), -1, -1); 381 | return true; 382 | } 383 | 384 | void Tetrahedralizer::tetrahedralize() 385 | { 386 | Parameters ¶ms = mesh.params; 387 | igl::Timer timer; 388 | 389 | timer.start(); 390 | simplify(input_vertices, input_faces, input_tags, *tree, params, skip_simplify); 391 | tree->init_b_mesh_and_tree(input_vertices, input_faces, mesh); 392 | logger().info("preprocessing {}s", timer.getElapsedTimeInSec()); 393 | logger().info(""); 394 | stats().record(StateInfo::preprocessing_id, timer.getElapsedTimeInSec(), input_vertices.size(), 395 | input_faces.size(), -1, -1); 396 | if (params.log_level <= 1) 397 | output_component(input_vertices, input_faces, input_tags); 398 | 399 | timer.start(); 400 | std::vector is_face_inserted(input_faces.size(), false); 401 | FloatTetDelaunay::tetrahedralize(input_vertices, input_faces, *tree, mesh, is_face_inserted); 402 | logger().info("#v = {}", mesh.get_v_num()); 403 | logger().info("#t = {}", mesh.get_t_num()); 404 | logger().info("tetrahedralizing {}s", timer.getElapsedTimeInSec()); 405 | logger().info(""); 406 | stats().record(StateInfo::tetrahedralization_id, timer.getElapsedTimeInSec(), mesh.get_v_num(), mesh.get_t_num(), 407 | -1, -1); 408 | 409 | timer.start(); 410 | insert_triangles(input_vertices, input_faces, input_tags, mesh, is_face_inserted, *tree, false); 411 | logger().info("cutting {}s", timer.getElapsedTimeInSec()); 412 | logger().info(""); 413 | stats().record(StateInfo::cutting_id, timer.getElapsedTimeInSec(), mesh.get_v_num(), mesh.get_t_num(), 414 | mesh.get_max_energy(), mesh.get_avg_energy(), 415 | std::count(is_face_inserted.begin(), is_face_inserted.end(), false)); 416 | 417 | timer.start(); 418 | optimization(input_vertices, input_faces, input_tags, is_face_inserted, mesh, *tree, {{1, 1, 1, 1}}); 419 | logger().info("mesh optimization {}s", timer.getElapsedTimeInSec()); 420 | logger().info(""); 421 | stats().record(StateInfo::optimization_id, timer.getElapsedTimeInSec(), mesh.get_v_num(), mesh.get_t_num(), 422 | mesh.get_max_energy(), mesh.get_avg_energy()); 423 | 424 | timer.start(); 425 | correct_tracked_surface_orientation(mesh, *tree); 426 | logger().info("correct_tracked_surface_orientation done"); 427 | } 428 | 429 | void Tetrahedralizer::save(const std::string &path, bool smooth_open_boundary, bool floodfill, bool manifold_surface, bool use_input_for_wn, bool correct_surface_orientation, bool all_mesh, bool binary, int boolean_op) 430 | { 431 | igl::Timer timer; 432 | 433 | Mesh mesh_copy = mesh; 434 | Eigen::MatrixXd Vout; 435 | Eigen::MatrixXi Fout; 436 | 437 | Parameters ¶ms = mesh_copy.params; 438 | params.output_path = path; 439 | params.correct_surface_orientation = correct_surface_orientation; 440 | params.smooth_open_boundary = smooth_open_boundary; 441 | params.manifold_surface = manifold_surface; 442 | params.use_input_for_wn = use_input_for_wn; 443 | 444 | if (params.output_path.empty()) 445 | params.output_path = params.input_path; 446 | if (params.log_path.empty()) 447 | params.log_path = params.output_path; 448 | 449 | std::string output_mesh_name = params.output_path; 450 | if (params.output_path.size() > 3 && params.output_path.substr(params.output_path.size() - 3, params.output_path.size()) == "msh") 451 | output_mesh_name = params.output_path; 452 | else if (params.output_path.size() > 4 && params.output_path.substr(params.output_path.size() - 4, params.output_path.size()) == "mesh") 453 | output_mesh_name = params.output_path; 454 | else 455 | output_mesh_name = params.output_path + "_" + params.postfix + ".msh"; 456 | 457 | if (!all_mesh) 458 | { 459 | if (has_json_csg) 460 | floatTetWild::boolean_operation(mesh_copy, tree_with_ids); 461 | else if (boolean_op >= 0) 462 | floatTetWild::boolean_operation(mesh_copy, boolean_op); 463 | else 464 | { 465 | if (params.smooth_open_boundary) 466 | { 467 | floatTetWild::smooth_open_boundary(mesh_copy, *tree); 468 | for (auto &t : mesh_copy.tets) 469 | { 470 | if (t.is_outside) 471 | t.is_removed = true; 472 | } 473 | } 474 | else 475 | { 476 | if (floodfill) 477 | filter_outside_floodfill(mesh_copy); 478 | else 479 | filter_outside(mesh_copy); 480 | } 481 | } 482 | if (params.manifold_surface) 483 | { 484 | floatTetWild::manifold_surface(mesh_copy, Vout, Fout); 485 | } 486 | } 487 | 488 | stats().record(StateInfo::wn_id, timer.getElapsedTimeInSec(), mesh_copy.get_v_num(), mesh_copy.get_t_num(), 489 | mesh_copy.get_max_energy(), mesh_copy.get_avg_energy()); 490 | logger().info("after winding number"); 491 | logger().info("#v = {}", mesh_copy.get_v_num()); 492 | logger().info("#t = {}", mesh_copy.get_t_num()); 493 | logger().info("winding number {}s", timer.getElapsedTimeInSec()); 494 | logger().info(""); 495 | 496 | if (params.output_path.size() > 3 && params.output_path.substr(params.output_path.size() - 3, params.output_path.size()) == "msh") 497 | MeshIO::write_mesh(params.output_path, mesh_copy, false, std::vector(), binary); 498 | else if (params.output_path.size() > 4 && params.output_path.substr(params.output_path.size() - 4, params.output_path.size()) == "mesh") 499 | MeshIO::write_mesh(params.output_path, mesh_copy, false, std::vector(), binary); 500 | else 501 | MeshIO::write_mesh(params.output_path + "_" + params.postfix + ".msh", mesh_copy, false, std::vector(), binary); 502 | } 503 | 504 | void Tetrahedralizer::get_tet_mesh(bool smooth_open_boundary, bool floodfill, bool manifold_surface, bool use_input_for_wn, bool correct_surface_orientation, bool all_mesh, Eigen::MatrixXd &Vs, Eigen::MatrixXi &Ts, Eigen::MatrixXd &flags, int boolean_op) 505 | { 506 | igl::Timer timer; 507 | 508 | Mesh mesh_copy = mesh; 509 | Eigen::MatrixXd V; 510 | Eigen::MatrixXi T; 511 | 512 | Eigen::MatrixXd Vout; 513 | Eigen::MatrixXi Fout; 514 | 515 | const auto skip_tet = [&mesh_copy](const int i) 516 | { return mesh_copy.tets[i].is_removed; }; 517 | const auto skip_vertex = [&mesh_copy](const int i) 518 | { return mesh_copy.tet_vertices[i].is_removed; }; 519 | std::vector t_ids(mesh_copy.tets.size()); 520 | std::iota(std::begin(t_ids), std::end(t_ids), 0); 521 | 522 | Parameters ¶ms = mesh_copy.params; 523 | params.correct_surface_orientation = correct_surface_orientation; 524 | params.smooth_open_boundary = smooth_open_boundary; 525 | params.manifold_surface = manifold_surface; 526 | params.use_input_for_wn = use_input_for_wn; 527 | 528 | if (!all_mesh) 529 | { 530 | if (has_json_csg) 531 | floatTetWild::boolean_operation(mesh_copy, tree_with_ids); 532 | else if (boolean_op >= 0) 533 | floatTetWild::boolean_operation(mesh_copy, boolean_op); 534 | else 535 | { 536 | if (params.smooth_open_boundary) 537 | { 538 | floatTetWild::smooth_open_boundary(mesh_copy, *tree); 539 | for (auto &t : mesh_copy.tets) 540 | { 541 | if (t.is_outside) 542 | t.is_removed = true; 543 | } 544 | } 545 | else 546 | { 547 | if (floodfill) 548 | filter_outside_floodfill(mesh_copy); 549 | else 550 | filter_outside(mesh_copy); 551 | } 552 | } 553 | 554 | if (params.manifold_surface) 555 | { 556 | floatTetWild::manifold_surface(mesh_copy, Vout, Fout); 557 | } 558 | 559 | stats().record(StateInfo::wn_id, timer.getElapsedTimeInSec(), mesh_copy.get_v_num(), mesh_copy.get_t_num(), 560 | mesh_copy.get_max_energy(), mesh_copy.get_avg_energy()); 561 | } 562 | logger().info("after winding number"); 563 | logger().info("#v = {}", mesh_copy.get_v_num()); 564 | logger().info("#t = {}", mesh_copy.get_t_num()); 565 | logger().info("winding number {}s", timer.getElapsedTimeInSec()); 566 | logger().info(""); 567 | 568 | int cnt_v = 0; 569 | std::map old_2_new; 570 | for (int i = 0; i < mesh_copy.tet_vertices.size(); i++) 571 | { 572 | if (!skip_vertex(i)) 573 | { 574 | old_2_new[i] = cnt_v; 575 | cnt_v++; 576 | } 577 | } 578 | int cnt_t = 0; 579 | for (const int i : t_ids) 580 | { 581 | if (!skip_tet(i)) 582 | cnt_t++; 583 | } 584 | 585 | V.resize(cnt_v, 3); 586 | int index = 0; 587 | for (size_t i = 0; i < mesh_copy.tet_vertices.size(); i++) 588 | { 589 | if (skip_vertex(i)) 590 | continue; 591 | V.row(index++) << mesh_copy.tet_vertices[i][0], mesh_copy.tet_vertices[i][1], mesh_copy.tet_vertices[i][2]; 592 | } 593 | 594 | T.resize(cnt_t, 4); 595 | flags.resize(cnt_t, 1); 596 | index = 0; 597 | 598 | const std::array new_indices = {{0, 1, 3, 2}}; 599 | 600 | for (const int i : t_ids) 601 | { 602 | if (skip_tet(i)) 603 | continue; 604 | for (int j = 0; j < 4; j++) 605 | { 606 | T(index, j) = old_2_new[mesh_copy.tets[i][new_indices[j]]]; 607 | } 608 | flags(index) = mesh_copy.tets[i].scalar; 609 | index++; 610 | } 611 | 612 | Eigen::MatrixXi I; 613 | igl::remove_unreferenced(V, T, Vs, Ts, I); 614 | } 615 | 616 | void Tetrahedralizer::get_tracked_surfaces(std::vector> &Vt, std::vector> &Ft) 617 | { 618 | if (has_json_csg) 619 | { 620 | int max_id = CSGTreeParser::get_max_id(tree_with_ids); 621 | 622 | for (int i = 0; i <= max_id; ++i) 623 | { 624 | Vt.emplace_back(); 625 | Ft.emplace_back(); 626 | 627 | get_tracked_surface(mesh, Vt.back(), Ft.back(), i); 628 | } 629 | } 630 | else 631 | { 632 | Vt.emplace_back(); 633 | Ft.emplace_back(); 634 | get_tracked_surface(mesh, Vt.back(), Ft.back()); 635 | } 636 | } 637 | 638 | std::string Tetrahedralizer::get_stats() const 639 | { 640 | std::stringstream ss; 641 | ss << stats(); 642 | return ss.str(); 643 | } 644 | 645 | #ifndef WILDMESHING_SKIP_BINDINGS 646 | void tetrahedralize(py::module &m) 647 | { 648 | auto &tetra = py::class_(m, "Tetrahedralizer") 649 | .def(py::init< 650 | double, int, int, int, 651 | double, double, 652 | bool, bool>(), 653 | py::arg("stop_quality") = 10, // "Specify max AMIPS energy for stopping mesh optimization" 654 | py::arg("max_its") = 80, // "Max number of mesh optimization iterations" 655 | py::arg("stage") = 2, // "Specify envelope stage" 656 | py::arg("stop_p") = -1, // 657 | py::arg("epsilon") = 1e-3, // "relative envelope epsilon_r. Absolute epsilonn = epsilon_r * diagonal_of_bbox" 658 | py::arg("edge_length_r") = 1. / 20., // "Relative target edge length l_r. Absolute l = l_r * diagonal_of_bbox" 659 | py::arg("skip_simplify") = false, // 660 | py::arg("coarsen") = true) 661 | 662 | .def( 663 | "set_log_level", [](Tetrahedralizer &t, int level) 664 | { t.set_log_level(level); }, 665 | "sets log level, valid value between 0 (all logs) and 6 (no logs)", py::arg("level")) 666 | 667 | .def( 668 | "load_mesh", [](Tetrahedralizer &t, const std::string &path, const std::vector &epsr_tags) 669 | { t.load_mesh(path, "", epsr_tags); }, 670 | "loads a mesh", py::arg("path"), py::arg("epsr_tags") = std::vector()) 671 | .def( 672 | "load_sizing_field", [](Tetrahedralizer &t, const std::string &path) 673 | { t.set_sizing_field(path); }, 674 | "load sizing field", py::arg("path")) 675 | .def( 676 | "set_sizing_field", [](Tetrahedralizer &t, const Eigen::MatrixXd &V, Eigen::MatrixXi &T, const Eigen::VectorXd &values) 677 | { t.set_sizing_field(V, T, values); }, 678 | "set sizing field", py::arg("V"), py::arg("T"), py::arg("values")) 679 | .def( 680 | "set_sizing_field_from_func", [](Tetrahedralizer &t, std::function &field) 681 | { t.set_sizing_field(field); }, 682 | "set sizing field", py::arg("field")) 683 | .def( 684 | "set_mesh", [](Tetrahedralizer &t, const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, const std::vector &epsr_tags) 685 | { t.set_mesh(V, F, epsr_tags); }, 686 | "sets a mesh", py::arg("V"), py::arg("F"), py::arg("epsr_tags") = std::vector()) 687 | .def( 688 | "set_meshes", [](Tetrahedralizer &t, const std::vector &V, const std::vector &F) 689 | { t.set_meshes(V, F); }, 690 | "sets several meshes, for boolean", py::arg("V"), py::arg("F")) 691 | .def( 692 | "load_csg_tree", [](Tetrahedralizer &t, const py::object &csg_tree) 693 | { 694 | const std::string tmp = py::str(csg_tree); 695 | t.boolean_operation(tmp); }, 696 | "loads a csg tree, either from file or json", py::arg("csg_tree")) 697 | 698 | .def( 699 | "tetrahedralize", [](Tetrahedralizer &t) 700 | { t.tetrahedralize(); }, 701 | "tetrahedralized the mesh") 702 | 703 | .def( 704 | "save", [](Tetrahedralizer &t, const std::string &path, bool smooth_open_boundary, bool floodfill, bool use_input_for_wn, bool manifold_surface, bool correct_surface_orientation, bool all_mesh, bool binary) 705 | { t.save(path, smooth_open_boundary, floodfill, use_input_for_wn, manifold_surface, correct_surface_orientation, all_mesh, binary); }, 706 | "saves the output", py::arg("path"), py::arg("smooth_open_boundary") = false, py::arg("floodfill") = false, py::arg("use_input_for_wn") = false, py::arg("manifold_surface") = false, py::arg("correct_surface_orientation") = false, py::arg("all_mesh") = false, py::arg("binary") = true) 707 | .def( 708 | "get_tet_mesh", [](Tetrahedralizer &t, bool smooth_open_boundary, bool floodfill, bool use_input_for_wn, bool manifold_surface, bool correct_surface_orientation, bool all_mesh) 709 | { 710 | Eigen::MatrixXd V; 711 | Eigen::MatrixXi T; 712 | Eigen::MatrixXd tags; 713 | t.get_tet_mesh(smooth_open_boundary, floodfill, manifold_surface, use_input_for_wn, correct_surface_orientation, all_mesh, V, T, tags); 714 | 715 | return py::make_tuple(V, T, tags); }, 716 | "gets the output", py::arg("smooth_open_boundary") = false, py::arg("floodfill") = false, py::arg("use_input_for_wn") = false, py::arg("manifold_surface") = false, py::arg("correct_surface_orientation") = false, py::arg("all_mesh") = false) 717 | .def( 718 | "get_tracked_surfaces", [](Tetrahedralizer &t) 719 | { 720 | std::vector> Vt; 721 | std::vector> Ft; 722 | Eigen::MatrixXd tags; 723 | t.get_tracked_surfaces(Vt, Ft); 724 | 725 | return py::make_tuple(Vt, Ft); }, 726 | "gets the tracked surfaces") 727 | .def( 728 | "get_tet_mesh_from_csg", [](Tetrahedralizer &t, const py::object &csg_tree, bool manifold_surface, bool use_input_for_wn, bool correct_surface_orientation) 729 | { 730 | Eigen::MatrixXd V; 731 | Eigen::MatrixXi T; 732 | Eigen::MatrixXd tags; 733 | const std::string tmp = py::str(csg_tree); 734 | t.tree_with_ids = json::parse(tmp); 735 | t.has_json_csg = true; 736 | 737 | t.get_tet_mesh(false, false, manifold_surface, use_input_for_wn, correct_surface_orientation, false, V, T, tags); 738 | 739 | t.has_json_csg = false; 740 | 741 | return py::make_tuple(V, T, tags); }, 742 | "gets the output from a csg tree", py::arg("csg_tree"), py::arg("manifold_surface") = false, py::arg("use_input_for_wn") = false, py::arg("correct_surface_orientation") = false) 743 | .def( 744 | "get_stats", [](const Tetrahedralizer &t) 745 | { return t.get_stats(); }, 746 | "returns the stats"); 747 | 748 | tetra.doc() = "Wildmeshing tetrahedralizer"; 749 | 750 | m.def( 751 | "tetrahedralize", [](const std::string &input, const std::string &output, double stop_quality, int max_its, int stage, int stop_p, double epsilon, double edge_length_r, bool mute_log, bool skip_simplify, bool coarsen, bool smooth_open_boundary, bool floodfill, bool use_input_for_wn, bool manifold_surface, bool correct_surface_orientation, bool all_mesh, bool binary) 752 | { 753 | wildmeshing_binding::init_globals(); 754 | 755 | static bool initialized = false; 756 | if (!initialized) 757 | { 758 | Logger::init(!mute_log); 759 | initialized = true; 760 | } 761 | 762 | Tetrahedralizer tetra(stop_quality, max_its, stage, stop_p, epsilon, edge_length_r, skip_simplify, coarsen); 763 | if (!tetra.load_mesh(input, "", std::vector())) 764 | return false; 765 | 766 | tetra.tetrahedralize(); 767 | tetra.save(output, smooth_open_boundary, floodfill, use_input_for_wn, manifold_surface, correct_surface_orientation, all_mesh, binary); 768 | 769 | return true; }, 770 | "Robust Tetrahedralization, this is an alpha developement version of TetWild. For a stable release refer to the C++ version https://github.com/Yixin-Hu/TetWild", 771 | py::arg("input"), // "Input surface mesh INPUT in .off/.obj/.stl/.ply format. (string, required)" 772 | // py::arg("postfix") = "" //"Add postfix into outputs' file name" 773 | py::arg("output") = "", // "Output tetmesh OUTPUT in .msh format. (string, optional, default: input_file+postfix+'.msh')" 774 | py::arg("stop_quality") = 10, // "Specify max AMIPS energy for stopping mesh optimization" 775 | py::arg("max_its") = 80, // "Max number of mesh optimization iterations" 776 | py::arg("stage") = 2, // "Specify envelope stage" 777 | py::arg("stop_p") = -1, // 778 | py::arg("epsilon") = 1e-3, // "relative envelope epsilon_r. Absolute epsilonn = epsilon_r * diagonal_of_bbox" 779 | py::arg("edge_length_r") = 1. / 20., // "Relative target edge length l_r. Absolute l = l_r * diagonal_of_bbox" 780 | py::arg("mute_log") = false, // "Mute prints"); 781 | py::arg("skip_simplify") = false, // 782 | py::arg("coarsen") = true, py::arg("smooth_open_boundary") = false, py::arg("floodfill") = false, py::arg("manifold_surface") = false, py::arg("use_input_for_wn") = false, py::arg("correct_surface_orientation") = false, py::arg("all_mesh") = false, py::arg("binary") = true); 783 | 784 | m.def( 785 | "boolean_operation", [](const py::object &json, const std::string &output, double stop_quality, int max_its, int stage, int stop_p, double epsilon, double edge_length_r, bool mute_log, bool skip_simplify, bool coarsen, bool manifold_surface, bool use_input_for_wn, bool correct_surface_orientation, bool all_mesh, bool binary) 786 | { 787 | wildmeshing_binding::init_globals(); 788 | 789 | static bool initialized = false; 790 | if (!initialized) 791 | { 792 | Logger::init(!mute_log); 793 | initialized = true; 794 | } 795 | 796 | Tetrahedralizer tetra(stop_quality, max_its, stage, stop_p, epsilon, edge_length_r, skip_simplify, coarsen); 797 | 798 | const std::string tmp = py::str(json); 799 | 800 | if (!tetra.boolean_operation(tmp)) 801 | return false; 802 | 803 | tetra.tetrahedralize(); 804 | tetra.save(output, false, false, manifold_surface, use_input_for_wn, correct_surface_orientation, all_mesh, binary); 805 | 806 | return true; }, 807 | "Robust boolean operation, this is an alpha developement version", 808 | py::arg("json"), // "Input surface mesh INPUT in .off/.obj/.stl/.ply format. (string, required)" 809 | // py::arg("postfix") = "" //"Add postfix into outputs' file name" 810 | py::arg("output") = "", // "Output tetmesh OUTPUT in .msh format. (string, optional, default: input_file+postfix+'.msh')" 811 | py::arg("stop_quality") = 10, // "Specify max AMIPS energy for stopping mesh optimization" 812 | py::arg("max_its") = 80, // "Max number of mesh optimization iterations" 813 | py::arg("stage") = 2, // "Specify envelope stage" 814 | py::arg("stop_p") = -1, // 815 | py::arg("epsilon") = 1e-3, // "relative envelope epsilon_r. Absolute epsilonn = epsilon_r * diagonal_of_bbox" 816 | py::arg("edge_length_r") = 1. / 20., // "Relative target edge length l_r. Absolute l = l_r * diagonal_of_bbox" 817 | py::arg("mute_log") = false, // "Mute prints"); 818 | py::arg("skip_simplify") = false, // 819 | py::arg("coarsen") = true, py::arg("manifold_surface") = false, py::arg("use_input_for_wn") = false, py::arg("correct_surface_orientation") = false, py::arg("all_mesh") = false, py::arg("binary") = true); 820 | } 821 | #endif 822 | } // namespace wildmeshing_binding -------------------------------------------------------------------------------- /src/tetrahedralize.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Utils.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | namespace wildmeshing_binding 15 | { 16 | class Tetrahedralizer 17 | { 18 | public: 19 | floatTetWild::Mesh mesh; 20 | 21 | std::vector input_vertices; 22 | std::vector input_faces; 23 | std::vector input_tags; 24 | GEO::Mesh sf_mesh; 25 | std::unique_ptr tree; 26 | bool skip_simplify; 27 | floatTetWild::json tree_with_ids; 28 | bool has_json_csg = false; 29 | 30 | Tetrahedralizer( 31 | double stop_quality, int max_its, int stage, int stop_p, 32 | double epsilon, double edge_length_r, 33 | bool skip_simplify, bool coarsen); 34 | 35 | private: 36 | void set_num_threads(int num_threads); 37 | 38 | public: 39 | void set_log_level(int level); 40 | 41 | bool boolean_operation(const std::string &json_string); 42 | void set_meshes(const std::vector &V, const std::vector &F); 43 | 44 | bool load_mesh(const std::string &path, const std::string &tag_path, const std::vector &epsr_tags); 45 | void set_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, const std::vector &epsr_tags); 46 | 47 | void set_sizing_field(const std::string &path); 48 | void set_sizing_field(const Eigen::MatrixXd &V, Eigen::MatrixXi &T, const Eigen::VectorXd &values); 49 | void set_sizing_field(std::function &field); 50 | 51 | private: 52 | void set_sizing_field(const Eigen::VectorXd &V_in, const Eigen::VectorXi &T_in, const Eigen::VectorXd &values); 53 | bool load_mesh_aux(); 54 | 55 | public: 56 | void tetrahedralize(); 57 | 58 | void save(const std::string &path, bool smooth_open_boundary, bool floodfill, bool manifold_surface, bool use_input_for_wn, bool correct_surface_orientation, bool all_mesh, bool binary, int boolean_op = -1); 59 | void get_tet_mesh(bool smooth_open_boundary, bool floodfill, bool manifold_surface, bool use_input_for_wn, bool correct_surface_orientation, bool all_mesh, Eigen::MatrixXd &Vs, Eigen::MatrixXi &Ts, Eigen::MatrixXd &flags, int boolean_op = -1); 60 | void get_tracked_surfaces(std::vector> &Vt, std::vector> &Ft); 61 | std::string get_stats() const; 62 | }; 63 | #ifndef WILDMESHING_SKIP_BINDINGS 64 | void tetrahedralize(py::module &m); 65 | #endif 66 | } // namespace wildmeshing_binding 67 | -------------------------------------------------------------------------------- /src/triangulate.cpp: -------------------------------------------------------------------------------- 1 | #include "triangulate.hpp" 2 | 3 | #include "triwild/optimization.h" 4 | #include "triwild/feature.h" 5 | #include "triwild/triangulation.h" 6 | #include "triwild/do_triwild.h" 7 | 8 | #include "triwild/meshio.hpp" 9 | 10 | #include 11 | 12 | 13 | namespace wildmeshing_binding 14 | { 15 | 16 | void triangulate(py::module &m) 17 | { 18 | 19 | m.def("triangulate", [](const std::string &input, const std::string &feature_input, const std::string &output, double stop_quality, int max_its, int stage, double epsilon, double feature_epsilon, double target_edge_len, double edge_length_r, double flat_feature_angle, bool cut_outside, bool skip_eps, const std::string &hole_file, bool mute_log) { 20 | init_globals(); 21 | 22 | using namespace triwild; 23 | using namespace std; 24 | 25 | args.input = input; 26 | // args.postfix = postfix; 27 | args.feature_input = feature_input; 28 | args.output = output; 29 | args.stop_quality = stop_quality; 30 | args.max_its = max_its; 31 | args.stage = stage; 32 | args.epsilon = epsilon; 33 | args.feature_epsilon = feature_epsilon; 34 | args.target_edge_len = target_edge_len; 35 | args.edge_length_r = edge_length_r; 36 | args.flat_feature_angle = flat_feature_angle; 37 | 38 | args.mute_log = mute_log; 39 | 40 | // app.add_option("--log-file", args.log_file, "Output a log file."); 41 | 42 | // bool diffusion_curves = false; 43 | // app.add_flag("--diffusion-curves", diffusion_curves, "Export for diffusion curves"); 44 | 45 | igl::Timer main_timer; 46 | double load_mesh_time = 0; 47 | double preprocess_time = 0; 48 | double bsp_time = 0; 49 | double optimization_time = 0; 50 | double curving_time = 0; 51 | double cut_and_hole_time = 0; 52 | 53 | #ifndef WIN32 54 | if (args.mute_log) 55 | { 56 | std::streambuf *orig_buf = cout.rdbuf(); 57 | cout.rdbuf(NULL); 58 | } 59 | #endif 60 | 61 | // if (args.output == "") 62 | // args.output = args.input + "_" + args.postfix; 63 | 64 | igl::Timer igl_timer; 65 | double t; 66 | /////////////////// 67 | cout << "Loading and preprocessing ..." << endl; 68 | igl_timer.start(); 69 | 70 | Eigen::MatrixXd V; 71 | std::vector> edges; 72 | triangulation::load_input(args.input, V, edges); 73 | int input_v_cnt = V.rows(); 74 | int input_e_cnt = edges.size(); 75 | load_mesh_time = igl_timer.getElapsedTime(); 76 | igl_timer.start(); 77 | 78 | if (feature::init(args.feature_input)) 79 | args.is_preserving_feature = true; 80 | 81 | if (args.stop_quality < 0) 82 | args.stop_quality = args.is_preserving_feature ? 20 : 10; 83 | if (args.epsilon < 0) 84 | // args.epsilon = args.is_preserving_feature ? 5e-3 : 1e-3; 85 | args.epsilon = args.is_preserving_feature ? 2e-3 : 1e-3; 86 | 87 | double line_width = 0.3 * args.diagonal_len; 88 | double f_line_width = 0.5 * args.diagonal_len; 89 | double s_f_line_width = 0.4 * args.diagonal_len; 90 | // double draw_points = true; 91 | double draw_points = false; 92 | 93 | double point_size = 0.0005 * args.diagonal_len; 94 | double f_point_size = 0.001 * args.diagonal_len; 95 | double s_f_point_size = 0.003 * args.diagonal_len; 96 | 97 | std::string line_col = "0 0 0"; 98 | std::string f_line_col = "0.9 0.3 0.2"; 99 | std::string s_f_line_col = "0.1 0.7 0.6"; 100 | 101 | std::string point_col = "0 0 0"; 102 | std::string f_point_col = "0.9 0.3 0.2"; 103 | std::string s_f_point_col = "0.1 0.7 0.6"; 104 | 105 | GEO::Mesh b_mesh; 106 | triangulation::preprocessing(V, edges, b_mesh); 107 | GEO::MeshFacetsAABB b_tree(b_mesh); 108 | t = igl_timer.getElapsedTime(); 109 | preprocess_time = t; 110 | cout << "Loaded and preprocessed." << endl; 111 | cout << "time = " << t << "s" << endl 112 | << endl; 113 | 114 | /////////////////// 115 | cout << "BSP subdivision..." << endl; 116 | igl_timer.start(); 117 | MeshData mesh; 118 | std::vector> tag_boundary_es; 119 | triangulation::BSP_subdivision(V, edges, mesh, tag_boundary_es, b_tree); 120 | t = igl_timer.getElapsedTime(); 121 | bsp_time = t; 122 | cout << "BSP subdivision done." << endl; 123 | cout << "time = " << t << "s" << endl 124 | << endl; 125 | 126 | /////////////////// 127 | cout << "Mesh optimization..." << endl; 128 | igl_timer.start(); 129 | optimization::init(V, edges, tag_boundary_es, mesh, b_tree); 130 | 131 | optimization::refine(mesh, b_tree, std::array({1, 1, 1, 1})); 132 | t = igl_timer.getElapsedTime(); 133 | optimization_time = t; 134 | cout << "Mesh optimization done." << endl; 135 | cout << "time = " << t << "s" << endl; 136 | 137 | // if (!skip_eps) { 138 | // export_eps(mesh, 139 | // line_width, line_col, point_size, point_col, 140 | // f_line_width, f_line_col, f_point_size, f_point_col, 141 | // s_f_line_width, s_f_line_col, s_f_point_size, s_f_point_col, 142 | // draw_points, args.output + "_lin.eps"); 143 | // } 144 | 145 | /////////////////// 146 | if (!args.is_preserving_feature) 147 | { 148 | optimization::output_mesh(mesh); 149 | 150 | if (args.log_file != "") 151 | { 152 | std::ofstream file(args.log_file); 153 | if (!file.fail()) 154 | { 155 | file << "load_mesh_time " << load_mesh_time << "\n"; 156 | file << "preprocess_time " << preprocess_time << "\n"; 157 | file << "bsp_time " << bsp_time << "\n"; 158 | file << "optimization_time " << optimization_time << "\n"; 159 | file << "curving_time " << curving_time << "\n"; 160 | file << "cut_and_hole_time " << cut_and_hole_time << "\n"; 161 | file << "input_v_cnt " << input_v_cnt << "\n"; 162 | file << "input_e_cnt " << input_e_cnt << "\n"; 163 | optimization::output_stats(mesh, file); 164 | } 165 | file.close(); 166 | } 167 | 168 | triwild::feature::features.clear(); 169 | triwild::feature::secondary_features.clear(); 170 | return; 171 | } 172 | 173 | cout << "Curving..." << endl; 174 | igl_timer.start(); 175 | feature::curving(mesh, b_tree); 176 | t = igl_timer.getElapsedTime(); 177 | curving_time = t; 178 | cout << "Curving done." << endl; 179 | cout << "time = " << t << "s" << endl; 180 | 181 | igl_timer.start(); 182 | if (cut_outside) 183 | optimization::erase_outside(mesh); 184 | if (hole_file != "") 185 | optimization::erase_holes(mesh, hole_file); 186 | cut_and_hole_time = igl_timer.getElapsedTime(); 187 | 188 | optimization::output_mesh(mesh); 189 | 190 | // if (diffusion_curves) 191 | // write_msh_DiffusionCurve(mesh, args.output + "DC"); 192 | 193 | if (!skip_eps) 194 | { 195 | export_eps(mesh, 196 | line_width, line_col, point_size, point_col, 197 | f_line_width, f_line_col, f_point_size, f_point_col, 198 | s_f_line_width, s_f_line_col, s_f_point_size, s_f_point_col, 199 | draw_points, args.output + ".eps"); 200 | 201 | // const double N = 10; 202 | // for(int i = 0; i <= N; ++i) 203 | // { 204 | // const double t = i/N; 205 | // const double t = i/N; 206 | // export_eps(mesh, 207 | // line_width, line_col, point_size, point_col, 208 | // f_line_width, f_line_col, f_point_size, f_point_col, 209 | // s_f_line_width, s_f_line_col, s_f_point_size, s_f_point_col, 210 | // draw_points, args.output + "_" + std::to_string(i) + ".eps", t); 211 | // } 212 | } 213 | 214 | if (args.log_file != "") 215 | { 216 | std::ofstream file(args.log_file); 217 | if (!file.fail()) 218 | { 219 | file << "load_mesh_time " << load_mesh_time << "\n"; 220 | file << "preprocess_time " << preprocess_time << "\n"; 221 | file << "bsp_time " << bsp_time << "\n"; 222 | file << "optimization_time " << optimization_time << "\n"; 223 | file << "curving_time " << curving_time << "\n"; 224 | file << "cut_and_hole_time " << cut_and_hole_time << "\n"; 225 | file << "input_v_cnt " << input_v_cnt << "\n"; 226 | file << "input_e_cnt " << input_e_cnt << "\n"; 227 | optimization::output_stats(mesh, file); 228 | feature::output_stats(mesh, file); 229 | } 230 | file.close(); 231 | } 232 | triwild::feature::features.clear(); 233 | triwild::feature::secondary_features.clear(); 234 | }, 235 | "Robust Triangulation", 236 | py::arg("input"), // "Input segments in .obj format" 237 | // py::arg("postfix") = "" //"Add postfix into outputs' file name" 238 | py::arg("feature_input") = "", // "Input feature json file" 239 | py::arg("output") = "", //"Output path" 240 | py::arg("stop_quality") = -1, // "Specify max AMIPS energy for stopping mesh optimization" 241 | py::arg("max_its") = 80, // "Max number of mesh optimization iterations" 242 | py::arg("stage") = 1, // "Specify envelope stage" 243 | py::arg("epsilon") = -1, // "relative envelope epsilon_r. Absolute epsilonn = epsilon_r * diagonal_of_bbox" 244 | py::arg("feature_epsilon") = 1e-3, // "Relative feature envelope mu_r. Absolute mu = mu_r * diagonal_of_bbox" 245 | py::arg("target_edge_len") = -1, // "Absolute target edge length l" 246 | py::arg("edge_length_r") = 1. / 20., // "Relative target edge length l_r. Absolute l = l_r * diagonal_of_bbox" 247 | py::arg("flat_feature_angle") = 10., // "Desired minimal angle" 248 | py::arg("cut_outside") = false, // "Remove \"outside part\"" 249 | py::arg("skip_eps") = false, // "Skip saving eps" 250 | py::arg("hole_file") = "", // "Input a .xyz file for specifying points inside holes you want to remove" 251 | py::arg("mute_log") = false // "Mute prints"); 252 | ); 253 | } 254 | } -------------------------------------------------------------------------------- /src/triangulate.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Utils.hpp" 4 | 5 | namespace wildmeshing_binding { 6 | void triangulate(py::module &m); 7 | } 8 | -------------------------------------------------------------------------------- /src/triangulate_data.cpp: -------------------------------------------------------------------------------- 1 | #include "triangulate_data.hpp" 2 | 3 | #include "triwild/optimization.h" 4 | #include "triwild/feature.h" 5 | #include "triwild/triangulation.h" 6 | #include "triwild/do_triwild.h" 7 | 8 | 9 | #include "triwild/meshio.hpp" 10 | 11 | #include 12 | 13 | 14 | namespace wildmeshing_binding 15 | { 16 | 17 | void triangulate_data(py::module &m) 18 | { 19 | m.def("triangulate_data", [](const Eigen::MatrixXd &V, const Eigen::MatrixXi &E, const py::object &feature_info, double stop_quality, int max_its, int stage, double epsilon, double feature_epsilon, double target_edge_len, double edge_length_r, double flat_feature_angle, bool cut_outside, bool skip_eps, const Eigen::MatrixXd &hole_pts, bool mute_log) { 20 | init_globals(); 21 | 22 | json jfeature_info; 23 | if (!feature_info.is(py::none())) 24 | { 25 | const std::string json_string = py::str(feature_info); 26 | jfeature_info = json::parse(json_string); 27 | } 28 | 29 | Eigen::MatrixXd V_out; 30 | Eigen::MatrixXi F_out; 31 | Eigen::MatrixXd nodes; 32 | std::vector> F_nodes; 33 | 34 | triwild::do_triwild(V, E, jfeature_info, 35 | V_out, F_out, nodes, F_nodes, 36 | stop_quality, max_its, stage, 37 | epsilon, feature_epsilon, 38 | target_edge_len, edge_length_r, 39 | flat_feature_angle, 40 | cut_outside, 41 | hole_pts, 42 | mute_log); 43 | 44 | triwild::feature::features.clear(); 45 | triwild::feature::secondary_features.clear(); 46 | return py::make_tuple(V_out, F_out, nodes, F_nodes); 47 | }, 48 | "Robust Triangulation", 49 | py::arg("V"), // "Input vertices" 50 | py::arg("E"), // "Input edges" 51 | py::arg("feature_info") = py::none(), // "Json string containing the features" 52 | py::arg("stop_quality") = -1, // "Specify max AMIPS energy for stopping mesh optimization" 53 | py::arg("max_its") = 80, // "Max number of mesh optimization iterations" 54 | py::arg("stage") = 1, // "Specify envelope stage" 55 | py::arg("epsilon") = -1, // "relative envelope epsilon_r. Absolute epsilonn = epsilon_r * diagonal_of_bbox" 56 | py::arg("feature_epsilon") = 1e-3, // "Relative feature envelope mu_r. Absolute mu = mu_r * diagonal_of_bbox" 57 | py::arg("target_edge_len") = -1, // "Absolute target edge length l" 58 | py::arg("edge_length_r") = 1. / 20., // "Relative target edge length l_r. Absolute l = l_r * diagonal_of_bbox" 59 | py::arg("flat_feature_angle") = 10., // "Desired minimal angle" 60 | py::arg("cut_outside") = false, // "Remove \"outside part\"" 61 | py::arg("skip_eps") = false, // "Skip saving eps" 62 | py::arg("hole_pts") = Eigen::MatrixXd(), // "Input a #n x 2 matrix of points inside holes you want to remove" 63 | py::arg("mute_log") = false // "Mute prints"); 64 | ); 65 | } 66 | } -------------------------------------------------------------------------------- /src/triangulate_data.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Utils.hpp" 4 | 5 | namespace wildmeshing_binding { 6 | void triangulate_data(py::module &m); 7 | } 8 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wildmeshing/wildmeshing-python/bc835076c1e2b2c92fe5364f5bc7f4119e6c5fd3/test/__init__.py -------------------------------------------------------------------------------- /test/small_tet_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | import numpy as np 4 | 5 | import wildmeshing as wm 6 | 7 | 8 | class TriangulateTest(unittest.TestCase): 9 | def test_tet(self): 10 | cubeV = np.array([[0, 0, 0], 11 | [1, 0, 0], 12 | [1, 1, 0], 13 | [0, 1, 0], 14 | [0, 0, 1], 15 | [1, 0, 1], 16 | [1, 1, 1], 17 | [0, 1, 1]], dtype=np.float64) 18 | 19 | cubeF = np.array([[0, 3, 2], 20 | [0, 2, 1], 21 | [4, 5, 6], 22 | [4, 6, 7], 23 | [1, 2, 6], 24 | [1, 6, 5], 25 | [0, 4, 7], 26 | [0, 7, 3], 27 | [0, 1, 5], 28 | [0, 5, 4], 29 | [2, 3, 7], 30 | [2, 7, 6]], dtype=np.int32) 31 | 32 | tetra = wm.Tetrahedralizer() 33 | tetra.set_mesh(cubeV, cubeF) 34 | tetra.tetrahedralize() 35 | VT, TT, _ = tetra.get_tet_mesh() 36 | 37 | def test_sizing_field(self): 38 | cubeV = np.array([[0, 0, 0], 39 | [1, 0, 0], 40 | [1, 1, 0], 41 | [0, 1, 0], 42 | [0, 0, 1], 43 | [1, 0, 1], 44 | [1, 1, 1], 45 | [0, 1, 1]], dtype=np.float64) 46 | 47 | cubeF = np.array([[0, 3, 2], 48 | [0, 2, 1], 49 | [4, 5, 6], 50 | [4, 6, 7], 51 | [1, 2, 6], 52 | [1, 6, 5], 53 | [0, 4, 7], 54 | [0, 7, 3], 55 | [0, 1, 5], 56 | [0, 5, 4], 57 | [2, 3, 7], 58 | [2, 7, 6]], dtype=np.int32) 59 | 60 | v = np.array([[-0.1, -0.1, -0.1], 61 | [1.1, -0.1, -0.1], 62 | [-0.1, 1.1, -0.1], 63 | [1.1, 1.1, -0.1], 64 | [-0.1, -0.1, 1.1], 65 | [1.1, -0.1, 1.1], 66 | [-0.1, 1.1, 1.1], 67 | [1.1, 1.1, 1.1]], dtype=np.float64) 68 | 69 | t = np.array([[5, 0, 3, 1], 70 | [5, 6, 0, 4], 71 | [5, 3, 6, 7], 72 | [0, 6, 3, 2], 73 | [5, 0, 6, 3]], dtype=np.int32) 74 | 75 | a = np.array([[0.1], 76 | [0.1], 77 | [0.1], 78 | [0.1], 79 | [0.1], 80 | [0.1], 81 | [0.1], 82 | [0.1]], dtype=np.float64) 83 | 84 | tetra = wm.Tetrahedralizer() 85 | tetra.set_mesh(cubeV, cubeF) 86 | tetra.set_sizing_field(v, t, a) 87 | tetra.tetrahedralize() 88 | VT, TT, _ = tetra.get_tet_mesh() 89 | 90 | 91 | if __name__ == '__main__': 92 | unittest.main() 93 | -------------------------------------------------------------------------------- /test/tet_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import json 3 | import platform 4 | 5 | import os 6 | 7 | import numpy as np 8 | import wildmeshing as wm 9 | 10 | # import igl 11 | # import meshplot as mp 12 | # mp.offline() 13 | 14 | 15 | class TetrahedralizeTest(unittest.TestCase): 16 | def test_doc(self): 17 | print(wm.tetrahedralize.__doc__) 18 | 19 | def test_run(self): 20 | root_folder = os.path.join("..", "3rdparty", "data") 21 | dir_path = os.path.dirname(os.path.realpath(__file__)) 22 | mesh_path = os.path.join(dir_path, root_folder, "small.stl") 23 | 24 | wm.tetrahedralize(mesh_path, "tet_test.msh", 25 | mute_log=True, stop_quality=1000) 26 | 27 | def test_data(self): 28 | root_folder = os.path.join(os.path.dirname( 29 | os.path.realpath(__file__)), "..", "3rdparty", "data") 30 | V = np.load(os.path.join(root_folder, "V.npy")) 31 | F = np.load(os.path.join(root_folder, "F.npy")) 32 | 33 | tetra = wm.Tetrahedralizer(stop_quality=1000) 34 | tetra.set_mesh(V, F) 35 | tetra.tetrahedralize() 36 | VT, TT, _ = tetra.get_tet_mesh() 37 | # mp.plot(VT, TT, filename="plot.html") 38 | 39 | def test_boolean(self): 40 | if platform.system() == "Windows": 41 | return 42 | 43 | root_folder = os.path.join(os.path.dirname( 44 | os.path.realpath(__file__)), "..", "3rdparty", "data") 45 | 46 | csg_tree = { 47 | "operation": "difference", 48 | "left": { 49 | "operation": "intersection", 50 | "left": os.path.join(root_folder, "csg_input", "box.stl"), 51 | "right": os.path.join(root_folder, "csg_input", "ball.stl") 52 | }, 53 | "right": { 54 | "operation": "union", 55 | "left": os.path.join(root_folder, "csg_input", "x.stl"), 56 | "right": { 57 | "operation": "union", 58 | "left": os.path.join(root_folder, "csg_input", "y.stl"), 59 | "right": os.path.join(root_folder, "csg_input", "z.stl") 60 | } 61 | } 62 | } 63 | 64 | wm.boolean_operation(json.dumps(csg_tree), "bool", stop_quality=1000) 65 | 66 | def test_boolean1(self): 67 | if platform.system() == "Windows": 68 | return 69 | 70 | root_folder = os.path.join(os.path.dirname( 71 | os.path.realpath(__file__)), "..", "3rdparty", "data") 72 | 73 | csg_tree = { 74 | "operation": "difference", 75 | "left": { 76 | "operation": "intersection", 77 | "left": os.path.join(root_folder, "csg_input", "box.stl"), 78 | "right": os.path.join(root_folder, "csg_input", "ball.stl") 79 | }, 80 | "right": { 81 | "operation": "union", 82 | "left": os.path.join(root_folder, "csg_input", "x.stl"), 83 | "right": { 84 | "operation": "union", 85 | "left": os.path.join(root_folder, "csg_input", "y.stl"), 86 | "right": os.path.join(root_folder, "csg_input", "z.stl") 87 | } 88 | } 89 | } 90 | 91 | tetra = wm.Tetrahedralizer(stop_quality=1000) 92 | tetra.load_csg_tree(json.dumps(csg_tree)) 93 | tetra.tetrahedralize() 94 | VT, TT, _ = tetra.get_tet_mesh() 95 | 96 | # mp.plot(VT, TT, filename="plot.html") 97 | 98 | 99 | if __name__ == '__main__': 100 | unittest.main() 101 | -------------------------------------------------------------------------------- /test/tri_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import os 4 | 5 | import wildmeshing as wm 6 | 7 | 8 | class TriangulateTest(unittest.TestCase): 9 | def test_run(self): 10 | print(wm.triangulate.__doc__) 11 | 12 | def test_run_no_features(self): 13 | root_folder = os.path.join("..", "3rdparty", "data") 14 | dir_path = os.path.dirname(os.path.realpath(__file__)) 15 | mesh_path = os.path.join(dir_path, root_folder, "rocket.obj") 16 | 17 | wm.triangulate(mesh_path, output="tri_test_no", mute_log=True) 18 | 19 | def test_run_with_features(self): 20 | root_folder = os.path.join("..", "3rdparty", "data") 21 | dir_path = os.path.dirname(os.path.realpath(__file__)) 22 | mesh_path = os.path.join(dir_path, root_folder, "rocket.obj") 23 | feature_path = os.path.join(dir_path, root_folder, "rocket.json") 24 | 25 | wm.triangulate(mesh_path, feature_path, output="tri_test_with", skip_eps=True, mute_log=True) 26 | 27 | def test_svg(self): 28 | root_folder = os.path.join("..", "3rdparty", "data") 29 | dir_path = os.path.dirname(os.path.realpath(__file__)) 30 | svg_path = os.path.join(dir_path, root_folder, "rocket.svg") 31 | wm.triangulate_svg(svg_path, mute_log=True) 32 | 33 | 34 | if __name__ == '__main__': 35 | unittest.main() 36 | -------------------------------------------------------------------------------- /wildmeshing/__init__.py: -------------------------------------------------------------------------------- 1 | from .wildmeshing import triangulate 2 | from .wildmeshing import triangulate_data 3 | from .wildmeshing import tetrahedralize 4 | from .wildmeshing import boolean_operation 5 | from .wildmeshing import Tetrahedralizer 6 | from .triangulate_svg import triangulate_svg 7 | -------------------------------------------------------------------------------- /wildmeshing/runners.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import wildmeshing as wm 4 | import os.path 5 | import numpy as np 6 | 7 | 8 | def triangulate(): 9 | parser = argparse.ArgumentParser() 10 | parser.add_argument("-i", "--input", type=str, default="", 11 | help="Input segments in .obj format.") 12 | 13 | parser.add_argument("-o", "--output", type=str, 14 | default="", help="Output path.") 15 | parser.add_argument("--feature-input", type=str, default="", 16 | help="Input feature json file.") 17 | parser.add_argument("--stop-energy", type=float, default=10, 18 | help="Specify max AMIPS energy for stopping mesh optimization.") 19 | parser.add_argument("-e", "--epsr", type=float, default=1e-3, 20 | help="relative envelope epsilon_r. Absolute epsilonn = epsilon_r * diagonal_of_bbox") 21 | parser.add_argument("--feature-envelope", type=float, default=0.001, 22 | help="Relative feature envelope mu_r. Absolute mu = mu_r * diagonal_of_bbox") 23 | parser.add_argument("-l", "--lr", type=float, default=0.05, 24 | help="ideal_edge_length = diag_of_bbox * L. (double, optional, default: 0.05)") 25 | parser.add_argument("--mute-log", type=bool, 26 | default=False, help="Mute prints.") 27 | parser.add_argument("--cut-outside", type=bool, default=True, 28 | help="Remove \"outside part\".") 29 | parser.add_argument("--skip-eps", type=bool, 30 | default=True, help="Skip saving eps.") 31 | 32 | parser.add_argument("--cut-holes", type=str, default="", 33 | help="Input a .xyz file for specifying points inside holes you want to remove.") 34 | 35 | args = parser.parse_args() 36 | 37 | extension = os.path.splitext(args.input)[1] 38 | 39 | if extension == ".svg": 40 | with np.load(args.cut_holes) as holes: 41 | V_out, F_out, nodes, F_nodes = wm.triangulate_svg(args.input, 42 | stop_quality=args.stop_energy, 43 | epsilon=args.epsr, 44 | feature_epsilon=args.feature_envelope, 45 | target_edge_len=args.lr, 46 | hole_pts=holes, 47 | cut_outside=args.cut_outside, 48 | skip_eps=args.skip_eps, 49 | mute_log=args.mute_log) 50 | np.savetxt(args.output + "_V.txt", V_out) 51 | np.savetxt(args.output + "_F.txt", F_out) 52 | np.savetxt(args.output + "_Vn.txt", nodes) 53 | np.savetxt(args.output + "_Fn.txt", F_nodes) 54 | else: 55 | wm.triangulate(args.input, 56 | feature_input=args.feature_input, 57 | stop_quality=args.stop_energy, 58 | epsilon=args.epsr, 59 | feature_epsilon=args.feature_envelope, 60 | target_edge_len=args.lr, 61 | hole_file=args.cut_holes, 62 | cut_outside=args.cut_outside, 63 | output=args.output, 64 | skip_eps=args.skip_eps, 65 | mute_log=args.mute_log) 66 | 67 | 68 | def tetrahedralize(): 69 | parser = argparse.ArgumentParser() 70 | 71 | parser.add_argument("-i", "--input", type=str, 72 | help="Input surface mesh INPUT in .off/.obj/.stl/.ply format. (string, required)") 73 | parser.add_argument("-o", "--output", type=str, 74 | help="Output tetmesh OUTPUT in .msh format. (string, optional, default: input_file+postfix+'.msh')") 75 | 76 | parser.add_argument("-l", "--lr", type=float, default=0.05, 77 | help="ideal_edge_length = diag_of_bbox * L. (double, optional, default: 0.05)") 78 | parser.add_argument("-e", "--epsr", type=float, default=1e-3, 79 | help="epsilon = diag_of_bbox * EPS. (double, optional, default: 1e-3)") 80 | 81 | parser.add_argument("--stop-energy", type=float, default=10, 82 | help="Stop optimization when max energy is lower than this.") 83 | 84 | parser.add_argument("--level", type=int, default=2, 85 | help="Log level (0 = most verbose, 6 = off).") 86 | 87 | parser.add_argument("--skip-simplify", type=bool, 88 | default=False, help="skip preprocessing.") 89 | parser.add_argument("--no-binary", type=bool, 90 | default=False, help="export meshes as ascii") 91 | 92 | parser.add_argument("--smooth-open-boundary", type=bool, 93 | default=False, help="Smooth the open boundary.") 94 | parser.add_argument("--manifold-surface", type=bool, 95 | default=False, help="Force the output to be manifold.") 96 | parser.add_argument("--coarsen", type=bool, default=True, 97 | help="Coarsen the output as much as possible.") 98 | parser.add_argument("--csg", type=str, default="", 99 | help="json file containg a csg tree") 100 | 101 | parser.add_argument("--disable-filtering", type=bool, default=False, 102 | help="Disable filtering out outside elements.") 103 | parser.add_argument("--use-floodfill", type=bool, default=False, 104 | help="Use flood-fill to extract interior volume.") 105 | parser.add_argument("--use-input-for-wn", type=bool, 106 | default=False, help="Use input surface for winding number.") 107 | 108 | parser.add_argument("--bg-mesh", type=str, default="", 109 | help="Background mesh for sizing field (.msh file).") 110 | parser.add_argument("--epsr-tags", type=str, default="", 111 | help="List of envelope size for each input faces.") 112 | 113 | args = parser.parse_args() 114 | 115 | tetra = wm.Tetrahedralizer(stop_quality=args.stop_energy, 116 | epsilon=args.epsr, 117 | edge_length_r=args.lr, 118 | skip_simplify=args.skip_simplify, 119 | coarsen=args.coarsen) 120 | tetra.set_log_level(args.level) 121 | 122 | if(len(args.bg_mesh) > 0): 123 | tetra.load_sizing_field(args.bg_mesh) 124 | 125 | if len(args.csg) > 0: 126 | with open(args.csg, "r") as f: 127 | data = json.load(f) 128 | tetra.load_csg_tree(data) 129 | else: 130 | tetra.load_mesh(args.input) 131 | tetra.tetrahedralize() 132 | 133 | tetra.save(args.output, 134 | all_mesh=args.disable_filtering, 135 | smooth_open_boundary=args.smooth_open_boundary, 136 | floodfill=args.use_floodfill, 137 | use_input_for_wn=args.use_input_for_wn, 138 | manifold_surface=args.manifold_surface, 139 | binary=not args.no_binary) 140 | -------------------------------------------------------------------------------- /wildmeshing/triangulate_svg.py: -------------------------------------------------------------------------------- 1 | import json 2 | import math 3 | import wildmeshing.parse_svg.svgpathtools as svg 4 | import numpy as np 5 | 6 | from wildmeshing import triangulate_data 7 | 8 | def triangulate_svg(svg_path, 9 | stop_quality = -1, 10 | max_its = 80, 11 | stage = 1, 12 | epsilon = -1, 13 | feature_epsilon = 1e-3, 14 | target_edge_len = -1, 15 | edge_length_r = 1./20., 16 | flat_feature_angle = 10., 17 | cut_outside = False, 18 | skip_eps = False, 19 | hole_pts = None, 20 | mute_log = False): 21 | vertices, lines, json = convert_svg(svg_path) 22 | 23 | if hole_pts is None: 24 | hole_pts = np.array([]) 25 | 26 | V_out, F_out, nodes, F_nodes = triangulate_data(vertices, lines, json, 27 | stop_quality, max_its, stage, 28 | epsilon, feature_epsilon, 29 | target_edge_len, edge_length_r, 30 | flat_feature_angle, 31 | cut_outside, 32 | skip_eps, 33 | hole_pts, 34 | mute_log 35 | ) 36 | 37 | assert (F_out.shape[0] == len(F_nodes)) 38 | for i in range(len(F_nodes)): 39 | F_nodes[i] = F_out[i, :].tolist() + F_nodes[i] 40 | 41 | return V_out, F_out, nodes, F_nodes 42 | 43 | 44 | 45 | 46 | 47 | 48 | class RationalBezier(object): 49 | def __init__(self, arc, tstart=0, tend=1): 50 | self.start = arc.point(tstart) 51 | self.end = arc.point(tend) 52 | 53 | s_tangentc = arc.unit_tangent(tstart) 54 | e_tangentc = arc.unit_tangent(tend) 55 | 56 | 57 | s_tangent = (arc.tf.dot( np.array([[s_tangentc.real], [s_tangentc.imag], [0.0]]))) 58 | e_tangent = (arc.tf.dot( np.array([[e_tangentc.real], [e_tangentc.imag], [0.0]]))) 59 | 60 | self.weights = np.array([1., math.cos((tend-tstart)*arc.delta/180.*math.pi/2.), 1.]) 61 | 62 | sx = self.start.real 63 | sy = self.start.imag 64 | 65 | ex = self.end.real 66 | ey = self.end.imag 67 | 68 | stx = s_tangent[0] 69 | sty = s_tangent[1] 70 | stn = math.sqrt(stx**2+sty**2) 71 | stx /= stn 72 | sty /= stn 73 | 74 | 75 | etx = e_tangent[0] 76 | ety = e_tangent[1] 77 | etn = math.sqrt(etx**2+ety**2) 78 | etx /= -etn 79 | ety /= -etn 80 | 81 | px = (((ey-sy)*stx+sty*sx)*etx-ety*ex*stx)/(etx*sty-ety*stx) 82 | py = (((-ex+sx)*sty-stx*sy)*ety+etx*ey*sty)/(etx*sty-ety*stx) 83 | 84 | self.control = px[0] + 1j*py[0] 85 | 86 | def __repr__(self): 87 | params = (self.start, self.control, self.end, self.weights) 88 | return ("RationalBezier(start={}, control={}, end={}, w={})".format(*params)) 89 | 90 | def __eq__(self, other): 91 | if not isinstance(other, RationalBezier): 92 | return NotImplemented 93 | return self.start == other.start and self.end == other.end \ 94 | and self.control == other.control \ 95 | and self.weights == other.weights 96 | 97 | def __ne__(self, other): 98 | if not isinstance(other, RationalBezier): 99 | return NotImplemented 100 | return not self == other 101 | 102 | def point(self, t): 103 | b0=(1-t)**2 104 | b1=2*(1-t)*t 105 | b2=t**2 106 | 107 | c0x = self.start.real 108 | c0y = self.start.imag 109 | 110 | c1x = self.control.real 111 | c1y = self.control.imag 112 | 113 | c2x = self.end.real 114 | c2y = self.end.imag 115 | 116 | denom = b0*self.weights[0]+b1*self.weights[1]+b2*self.weights[2] 117 | 118 | vx = (b0*c0x*self.weights[0]+b1*c1x*self.weights[1]+b2*c2x*self.weights[2])/denom 119 | vy = (b0*c0y*self.weights[0]+b1*c1y*self.weights[1]+b2*c2y*self.weights[2])/denom 120 | 121 | return vx + 1j*vy 122 | 123 | 124 | SAMPLE_MAX_DEPTH = 5 125 | SAMPLE_ERROR = 1e-3 126 | 127 | 128 | def complex_to_point(p): 129 | return [p.real, p.imag] 130 | 131 | 132 | def complex_to_vect(p): 133 | return [p.real, p.imag] 134 | 135 | 136 | def compute_samples(curve, start=0, end=1, error=SAMPLE_ERROR, max_depth=SAMPLE_MAX_DEPTH, depth=0): 137 | return compute_samples_aux(curve, start, end, curve.point(start), curve.point(end), error, max_depth, depth) 138 | 139 | 140 | def compute_samples_aux(curve, start, end, start_point, end_point, error, max_depth, depth): 141 | """Recursively approximates the length by straight lines""" 142 | mid = (start + end)/2 143 | mid_point = curve.point(mid) 144 | length = abs(end_point - start_point) 145 | first_half = abs(mid_point - start_point) 146 | second_half = abs(end_point - mid_point) 147 | 148 | 149 | length2 = first_half + second_half 150 | 151 | res = [start, mid, end] 152 | 153 | if abs(length) < 1e-10: 154 | return [] 155 | 156 | if depth < 2 or ((abs(length2 - length) > error) and (depth <= max_depth)): 157 | depth += 1 158 | res1 = compute_samples_aux(curve, start, mid, start_point, mid_point, error, max_depth, depth) 159 | res2 = compute_samples_aux(curve, mid, end, mid_point, end_point, error, max_depth, depth) 160 | return sorted(set(res1 + res2)) 161 | else: 162 | # This is accurate enough. 163 | return res 164 | 165 | 166 | def convert_svg(input_svg): 167 | doc = svg.Document(input_svg) 168 | paths = doc.flatten_all_paths() 169 | 170 | json_data = [] 171 | vertices = [] 172 | lines = [] 173 | 174 | v_index = 1 175 | c_index = 0 176 | for ii in range(len(paths)): 177 | tmp = paths[ii] 178 | path = tmp.path 179 | 180 | print(str(ii+1) + "/" + str(len(paths)) + " n sub paths: " + str(len(path))) 181 | 182 | first = True 183 | for pieces in path: 184 | if isinstance(pieces, svg.path.Arc): 185 | if first: 186 | if abs(pieces.start - path[-1].end) < 1e-10: 187 | pieces.start = path[-1].end 188 | if abs(pieces.delta) > 120: 189 | n_pices = math.ceil(abs(pieces.delta) / 120) 190 | tmp = [] 191 | for p in range(n_pices): 192 | t0 = float(p)/n_pices 193 | t1 = float(p+1)/n_pices 194 | tmp.append(RationalBezier(pieces, tstart=t0, tend=t1)) 195 | pieces = tmp 196 | else: 197 | pieces = [RationalBezier(pieces)] 198 | else: 199 | pieces = [pieces] 200 | 201 | first = False 202 | 203 | 204 | for piece in pieces: 205 | ts = compute_samples(piece) 206 | 207 | if len(ts) <= 0: 208 | continue 209 | 210 | 211 | param_ts = [] 212 | 213 | iprev = None 214 | 215 | for param_t in ts: 216 | param_ts.append(param_t) 217 | p = piece.point(param_t) 218 | xy = complex_to_point(p) 219 | 220 | if not iprev: 221 | istart = v_index 222 | 223 | vertices += [[xy[0], xy[1]]] 224 | 225 | if iprev: 226 | lines += [[iprev-1, v_index-1]] 227 | 228 | iprev = v_index 229 | 230 | v_index += 1 231 | 232 | json_obj = {} 233 | json_obj["v_ids"] = list(range(istart-1, v_index-1)) 234 | istart = None 235 | json_obj["paras"] = param_ts 236 | json_obj["curve_id"] = c_index 237 | c_index += 1 238 | 239 | is_set = False 240 | 241 | if isinstance(piece, svg.path.Line): 242 | json_obj["type"] = "Line" 243 | json_obj["start"] = complex_to_point(piece.start) 244 | json_obj["end"] = complex_to_point(piece.end) 245 | 246 | is_set = True 247 | elif isinstance(piece, svg.path.QuadraticBezier): 248 | json_obj["type"] = "BezierCurve" 249 | json_obj["degree"] = 2 250 | 251 | json_obj["poles"] = [complex_to_point(piece.start), complex_to_point(piece.control), complex_to_point(piece.end)] 252 | 253 | is_set = True 254 | elif isinstance(piece, svg.path.CubicBezier): 255 | json_obj["type"] = "BezierCurve" 256 | json_obj["degree"] = 3 257 | json_obj["poles"] = [complex_to_point(piece.start), complex_to_point(piece.control1), complex_to_point(piece.control2), complex_to_point(piece.end)] 258 | 259 | is_set = True 260 | elif isinstance(piece, RationalBezier): 261 | json_obj["type"] = "RationalBezier" 262 | json_obj["poles"] = [complex_to_point(piece.start), complex_to_point(piece.control), complex_to_point(piece.end)] 263 | json_obj["weigths"] = [piece.weights[0], piece.weights[1], piece.weights[2]] 264 | 265 | is_set = True 266 | 267 | if is_set: 268 | json_data.append(json_obj) 269 | else: 270 | print(type(piece)) 271 | print(piece) 272 | assert(False) 273 | 274 | 275 | return np.array(vertices), np.array(lines), json.dumps(json_data, indent=1) 276 | 277 | 278 | 279 | --------------------------------------------------------------------------------