├── .github └── workflows │ ├── conan-package.yml │ ├── process-pull-request.yml │ ├── unit-test-post.yml │ └── unit-test.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── conandata.yml ├── conanfile.py ├── doc └── img │ ├── example.svg │ └── slic3r_screenshot.png ├── examples ├── CMakeLists.txt └── main.cpp ├── include └── libnest2d │ ├── backends │ └── clipper │ │ ├── clipper_polygon.hpp │ │ └── geometries.hpp │ ├── common.hpp │ ├── geometry_traits.hpp │ ├── geometry_traits_nfp.hpp │ ├── libnest2d.hpp │ ├── nester.hpp │ ├── optimizer.hpp │ ├── optimizers │ ├── nlopt │ │ ├── genetic.hpp │ │ ├── nlopt_boilerplate.hpp │ │ ├── simplex.hpp │ │ └── subplex.hpp │ └── optimlib │ │ └── particleswarm.hpp │ ├── parallel.hpp │ ├── placers │ ├── bottomleftplacer.hpp │ ├── nfpplacer.hpp │ └── placer_boilerplate.hpp │ ├── selections │ ├── djd_heuristic.hpp │ ├── filler.hpp │ ├── firstfit.hpp │ └── selection_boilerplate.hpp │ └── utils │ ├── bigint.hpp │ ├── boost_alg.hpp │ ├── metaloop.hpp │ ├── rational.hpp │ ├── rotcalipers.hpp │ └── rotfinder.hpp ├── src └── libnest2d.cpp ├── test_package ├── CMakeLists.txt ├── conanfile.py └── main.cpp ├── tests ├── CMakeLists.txt └── test.cpp └── tools ├── benchmark.h ├── libnfpglue.cpp ├── libnfpglue.hpp ├── libnfporb ├── LICENSE ├── ORIGIN ├── README.md └── libnfporb.hpp ├── nfp_svgnest.hpp ├── nfp_svgnest_glue.hpp ├── printer_parts.cpp ├── printer_parts.hpp └── svgtools.hpp /.github/workflows/conan-package.yml: -------------------------------------------------------------------------------- 1 | name: conan-package 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'src/**' 7 | - 'include/**' 8 | - 'test_package/**' 9 | - 'tests/**' 10 | - 'cmake/**' 11 | - 'conanfile.py' 12 | - 'conandata.yml' 13 | - 'CMakeLists.txt' 14 | - 'requirements.txt' 15 | - '.github/workflows/conan-package.yml' 16 | - '.github/workflows/requirements*' 17 | branches: 18 | - main 19 | - 'CURA-*' 20 | - 'PP-*' 21 | - 'NP-*' 22 | - '[0-9].[0-9]*' 23 | - '[0-9].[0-9][0-9]*' 24 | 25 | jobs: 26 | conan-package: 27 | uses: ultimaker/cura-workflows/.github/workflows/conan-package.yml@main 28 | secrets: inherit 29 | -------------------------------------------------------------------------------- /.github/workflows/process-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: process-pull-request 2 | 3 | on: 4 | pull_request_target: 5 | types: [opened, reopened, edited, synchronize, review_requested, ready_for_review, assigned] 6 | 7 | jobs: 8 | add_label: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions-ecosystem/action-add-labels@v1 13 | if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} 14 | with: 15 | labels: 'PR: Community Contribution :crown:' 16 | -------------------------------------------------------------------------------- /.github/workflows/unit-test-post.yml: -------------------------------------------------------------------------------- 1 | name: unit-test-post 2 | 3 | on: 4 | workflow_run: 5 | workflows: [ unit-test ] 6 | types: [ completed ] 7 | 8 | jobs: 9 | publish-test-results: 10 | uses: ultimaker/cura-workflows/.github/workflows/unit-test-post.yml@main 11 | with: 12 | workflow_run_json: ${{ toJSON(github.event.workflow_run) }} 13 | secrets: inherit 14 | -------------------------------------------------------------------------------- /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | name: unit-test 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'src/**' 7 | - 'include/**' 8 | - 'tests' 9 | - 'test_package/**' 10 | - 'tests/**' 11 | - 'cmake/**' 12 | - 'conanfile.py' 13 | - 'conandata.yml' 14 | - 'CMakeLists.txt' 15 | - 'requirements.txt' 16 | - '.github/workflows/unit-test.yml' 17 | - '.github/workflows/requirements*' 18 | branches: 19 | - main 20 | - 'CURA-*' 21 | - 'PP-*' 22 | - 'NP-*' 23 | - '[0-9].[0-9]*' 24 | - '[0-9].[0-9][0-9]*' 25 | pull_request: 26 | paths: 27 | - 'src/**' 28 | - 'include/**' 29 | - 'tests' 30 | - 'test_package/**' 31 | - 'tests/**' 32 | - 'cmake/**' 33 | - 'conanfile.py' 34 | - 'conandata.yml' 35 | - 'CMakeLists.txt' 36 | - 'requirements.txt' 37 | - '.github/workflows/unit-test.yml' 38 | - '.github/workflows/requirements*' 39 | branches: 40 | - main 41 | - 'CURA-*' 42 | - 'PP-*' 43 | - 'NP-*' 44 | - '[0-9].[0-9]*' 45 | - '[0-9].[0-9][0-9]*' 46 | 47 | jobs: 48 | testing: 49 | name: Run unit tests 50 | uses: ultimaker/cura-workflows/.github/workflows/unit-test.yml@main 51 | with: 52 | test_use_ctest: true 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### C++ template 2 | # Prerequisites 3 | *.d 4 | 5 | # Compiled Object files 6 | *.slo 7 | *.lo 8 | *.o 9 | *.obj 10 | 11 | # Precompiled Headers 12 | *.gch 13 | *.pch 14 | 15 | # Compiled Dynamic libraries 16 | *.so 17 | *.dylib 18 | *.dll 19 | 20 | # Fortran module files 21 | *.mod 22 | *.smod 23 | 24 | # Compiled Static libraries 25 | *.lai 26 | *.la 27 | *.a 28 | *.lib 29 | 30 | # Executables 31 | *.exe 32 | *.out 33 | *.app 34 | 35 | ### CMake template 36 | CMakeLists.txt.user 37 | CMakeCache.txt 38 | CMakeFiles 39 | CMakeScripts 40 | Testing 41 | Makefile 42 | cmake_install.cmake 43 | install_manifest.txt 44 | compile_commands.json 45 | CTestTestfile.cmake 46 | _deps 47 | 48 | ### JetBrains template 49 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 50 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 51 | 52 | # User-specific stuff 53 | .idea/**/workspace.xml 54 | .idea/**/tasks.xml 55 | .idea/**/usage.statistics.xml 56 | .idea/**/dictionaries 57 | .idea/**/shelf 58 | 59 | # Generated files 60 | .idea/**/contentModel.xml 61 | 62 | # Sensitive or high-churn files 63 | .idea/**/dataSources/ 64 | .idea/**/dataSources.ids 65 | .idea/**/dataSources.local.xml 66 | .idea/**/sqlDataSources.xml 67 | .idea/**/dynamic.xml 68 | .idea/**/uiDesigner.xml 69 | .idea/**/dbnavigator.xml 70 | 71 | # Gradle 72 | .idea/**/gradle.xml 73 | .idea/**/libraries 74 | 75 | # Gradle and Maven with auto-import 76 | # When using Gradle or Maven with auto-import, you should exclude module files, 77 | # since they will be recreated, and may cause churn. Uncomment if using 78 | # auto-import. 79 | # .idea/artifacts 80 | # .idea/compiler.xml 81 | # .idea/jarRepositories.xml 82 | # .idea/modules.xml 83 | # .idea/*.iml 84 | # .idea/modules 85 | # *.iml 86 | # *.ipr 87 | 88 | # CMake 89 | cmake-build-*/ 90 | 91 | # Mongo Explorer plugin 92 | .idea/**/mongoSettings.xml 93 | 94 | # File-based project format 95 | *.iws 96 | 97 | # IntelliJ 98 | out/ 99 | 100 | # mpeltonen/sbt-idea plugin 101 | .idea_modules/ 102 | 103 | # JIRA plugin 104 | atlassian-ide-plugin.xml 105 | 106 | # Cursive Clojure plugin 107 | .idea/replstate.xml 108 | 109 | # Crashlytics plugin (for Android Studio and IntelliJ) 110 | com_crashlytics_export_strings.xml 111 | crashlytics.properties 112 | crashlytics-build.properties 113 | fabric.properties 114 | 115 | # Editor-based Rest Client 116 | .idea/httpRequests 117 | 118 | # Android studio 3.1+ serialized cache file 119 | .idea/caches/build_file_checksums.ser 120 | 121 | .idea/ 122 | tmp/* 123 | test_package/build 124 | 125 | CMakeUserPresets.json 126 | conaninfo.txt 127 | conanbuildinfo.txt 128 | conan.lock 129 | build/ 130 | graph_info.json 131 | /test_package/deactivate_conanrun.sh 132 | /test_package/nest2d-release-x86_64-data.cmake 133 | /test_package/CMakeFiles/ 134 | /test_package/test 135 | /test_package/NLopt-release-x86_64-data.cmake 136 | /test_package/nest2dTargets.cmake 137 | /test_package/.ninja_log 138 | /test_package/BoostConfig.cmake 139 | /test_package/nest2d-Target-release.cmake 140 | /test_package/clipper-config.cmake 141 | /test_package/clipper-config-version.cmake 142 | /test_package/conanrun.sh 143 | /test_package/Boost-Target-release.cmake 144 | /test_package/clipperTargets.cmake 145 | /test_package/CMakePresets.json 146 | /test_package/metadata/ 147 | /test_package/NLoptTargets.cmake 148 | /test_package/nest2d-config-version.cmake 149 | /test_package/.ninja_deps 150 | /test_package/NLoptConfigVersion.cmake 151 | /test_package/clipper-Target-release.cmake 152 | /test_package/conandeps_legacy.cmake 153 | /test_package/deactivate_conanbuildenv-release-x86_64.sh 154 | /test_package/BoostTargets.cmake 155 | /test_package/CMakeCache.txt 156 | /test_package/deactivate_conanbuild.sh 157 | /test_package/nest2d-config.cmake 158 | /test_package/conanrunenv-release-x86_64.sh 159 | /test_package/conanbuildenv-release-x86_64.sh 160 | /test_package/clipper-release-x86_64-data.cmake 161 | /test_package/NLoptConfig.cmake 162 | /test_package/Boost-release-x86_64-data.cmake 163 | /test_package/build.ninja 164 | /test_package/cmakedeps_macros.cmake 165 | /test_package/cmake_install.cmake 166 | /test_package/conan_toolchain.cmake 167 | /test_package/BoostConfigVersion.cmake 168 | /test_package/NLopt-Target-release.cmake 169 | /test_package/deactivate_conanrunenv-release-x86_64.sh 170 | /test_package/conanbuild.sh 171 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | cmake_policy(SET CMP0091 NEW) 3 | project(libnest2d) 4 | find_package(standardprojectsettings REQUIRED) 5 | 6 | option(BUILD_SHARED_LIBS "Build shared libs instead of static (applies for dependencies as well)" OFF) 7 | option(HEADER_ONLY "If enabled static library will not be built." ON) 8 | option(ENABLE_TESTING "Build with Google unittest" OFF) 9 | set(GEOMETRIES clipper CACHE STRING "Geometry backend, available options: 'clipper' (default), 'boost'") 10 | set(OPTIMIZER nlopt CACHE STRING "Optimization backend, available options: 'nlopt' (default), 'optimlib'") 11 | set(THREADING std CACHE STRING "Multithreading, available options: 'std' (default), 'tbb', 'omp', 'none'") 12 | 13 | add_library(project_options INTERFACE) 14 | set_project_warnings(project_options) 15 | 16 | set(nest2d_HDRS 17 | include/libnest2d/libnest2d.hpp 18 | include/libnest2d/nester.hpp 19 | include/libnest2d/geometry_traits.hpp 20 | include/libnest2d/geometry_traits_nfp.hpp 21 | include/libnest2d/common.hpp 22 | include/libnest2d/optimizer.hpp 23 | include/libnest2d/parallel.hpp 24 | include/libnest2d/utils/metaloop.hpp 25 | include/libnest2d/utils/rotfinder.hpp 26 | include/libnest2d/utils/rotcalipers.hpp 27 | include/libnest2d/utils/bigint.hpp 28 | include/libnest2d/utils/rational.hpp 29 | include/libnest2d/utils/boost_alg.hpp 30 | include/libnest2d/placers/placer_boilerplate.hpp 31 | include/libnest2d/placers/bottomleftplacer.hpp 32 | include/libnest2d/placers/nfpplacer.hpp 33 | include/libnest2d/selections/selection_boilerplate.hpp 34 | include/libnest2d/selections/filler.hpp 35 | include/libnest2d/selections/firstfit.hpp 36 | include/libnest2d/selections/djd_heuristic.hpp 37 | ) 38 | 39 | if("${GEOMETRIES}" STREQUAL "clipper") 40 | find_package(clipper REQUIRED) 41 | find_package(Boost REQUIRED) 42 | target_link_libraries(project_options INTERFACE clipper::clipper boost::boost) 43 | list(APPEND nest2d_HDRS 44 | include/libnest2d/clipper/clipper_polygon.hpp 45 | include/libnest2d/clipper/geometries.hpp 46 | ) 47 | elseif("${GEOMETRIES}" STREQUAL "boost") 48 | find_package(Boost REQUIRED) 49 | target_link_libraries(project_options INTERFACE boost::boost) 50 | else() 51 | message(FATAL_ERROR "Unknown GEOMETRIES: ${GEOMETRIES} specified; use one of the following: 'clipper' (default), 'boost'") 52 | endif() 53 | target_compile_definitions(project_options INTERFACE LIBNEST2D_GEOMETRIES_${GEOMETRIES}) 54 | 55 | if("${OPTIMIZER}" STREQUAL "nlopt") 56 | find_package(NLopt REQUIRED) 57 | target_link_libraries(project_options INTERFACE NLopt::nlopt) 58 | list(APPEND nest2d_HDRS 59 | include/libnest2d/optimizers/nlopt/simplex.hpp 60 | include/libnest2d/optimizers/nlopt/subplex.hpp 61 | include/libnest2d/optimizers/nlopt/genetic.hpp 62 | include/libnest2d/optimizers/nlopt/nlopt_boilerplate.hpp 63 | ) 64 | elseif("${OPTIMIZER}" STREQUAL "optimlib") 65 | find_package(armadillo REQUIRED) 66 | target_link_libraries(project_options INTERFACE armadillo::armadillo) 67 | list(APPEND nest2d_HDRS 68 | include/libnest2d/optimizers/optimlib/particleswarm.hpp 69 | ) 70 | else() 71 | message(FATAL_ERROR "Unknown OPTIMIZER: ${OPTIMIZER} specified; use one of the following: 'nlopt' (default), 'optimlib'") 72 | endif() 73 | target_compile_definitions(project_options INTERFACE LIBNEST2D_OPTIMIZER_${OPTIMIZER}) 74 | 75 | if("${THREADING}" STREQUAL "std") 76 | use_threads(project_options) 77 | elseif("${THREADING}" STREQUAL "tbb") 78 | find_package(TBB REQUIRED) 79 | target_link_libraries(project_options INTERFACE tbb::tbb) 80 | target_compile_definitions(project_options INTERFACE TBB_USE_CAPTURED_EXCEPTION) 81 | elseif("${THREADING}" STREQUAL "omp") 82 | find_package(OpenMP REQUIRED) 83 | target_link_libraries(project_options INTERFACE OpenMP::OpenMP) 84 | elseif("${THREADING}" STREQUAL "none") 85 | message(WARNING "Compiling without threading support") 86 | else() 87 | message(FATAL_ERROR "Unknown OPTIMIZER: ${OPTIMIZER} specified; use one of the following: 'nlopt' (default), 'optimlib'") 88 | endif() 89 | target_compile_definitions(project_options INTERFACE LIBNEST2D_THREADING_${THREADING}) 90 | 91 | set(libnest2d_SRCS 92 | src/libnest2d.cpp 93 | ) 94 | 95 | if(HEADER_ONLY) 96 | add_library(nest2d INTERFACE ${libnest2d_HDRS}) 97 | target_link_libraries(nest2d INTERFACE project_options) 98 | target_include_directories(nest2d 99 | INTERFACE 100 | $ 101 | $ 102 | ) 103 | else() 104 | if(BUILD_SHARED_LIBS) 105 | add_library(nest2d SHARED ${libnest2d_SRCS} ${libnest2d_HDRS}) 106 | if(WIN32) 107 | set_target_properties(nest2d PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) 108 | endif() 109 | else() 110 | add_library(nest2d STATIC ${libnest2d_SRCS} ${libnest2d_HDRS}) 111 | endif() 112 | target_link_libraries(nest2d PUBLIC project_options) 113 | target_include_directories(nest2d 114 | PUBLIC 115 | $ 116 | $ 117 | PRIVATE 118 | $ 119 | ) 120 | endif() 121 | 122 | if(ENABLE_TESTING) 123 | enable_testing() 124 | add_subdirectory(tests) 125 | endif() 126 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libnest2d 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |

21 | 22 | > **Notice:** 23 | > This library was developed as part of the [PrusaSlicer](https://github.com/prusa3d/PrusaSlicer) project. 24 | > **You can find the original version [here](https://github.com/prusa3d/PrusaSlicer/tree/master/src/libnest2d).** 25 | > This repository is a continuation of the original project (effectively a fork) that contains backported stable changes and is open to 26 | > further development. 27 | 28 | ## License 29 | 30 | ![License](https://img.shields.io/github/license/ultimaker/libnest2d?style=flat) 31 | libnest2d is released under terms of the LGPLv3 License. Terms of the license can be found in the LICENSE file. Or at 32 | http://www.gnu.org/licenses/lgpl.html 33 | 34 | > But in general it boils down to: 35 | > **You need to share the source of any libnest2d modifications if you make an application with libnest2d.** 36 | 37 | ## System Requirements 38 | 39 | ### Windows 40 | - Python 3.6 or higher 41 | - Ninja 1.10 or higher 42 | - VS2022 or higher 43 | - CMake 3.23 or higher 44 | - nmake 45 | 46 | ### MacOs 47 | - Python 3.6 or higher 48 | - Ninja 1.10 or higher 49 | - apply clang 11 or higher 50 | - CMake 3.23 or higher 51 | - make 52 | 53 | ### Linux 54 | - Python 3.6 or higher 55 | - Ninja 1.10 or higher 56 | - gcc 12 or higher 57 | - CMake 3.23 or higher 58 | - make 59 | 60 | 61 | ## How To Build 62 | 63 | > **Note:** 64 | > We are currently in the process of switch our builds and pipelines to an approach which uses [Conan](https://conan.io/) 65 | > and pip to manage our dependencies, which are stored on our JFrog Artifactory server and in the pypi.org. 66 | > At the moment not everything is fully ported yet, so bare with us. 67 | 68 | If you want to develop Cura with libnest2d see the Cura Wiki: [Running Cura from source](https://github.com/Ultimaker/Cura/wiki/Running-Cura-from-Source) 69 | 70 | If you have never used [Conan](https://conan.io/) read their [documentation](https://docs.conan.io/en/latest/index.html) 71 | which is quite extensive and well maintained. Conan is a Python program and can be installed using pip 72 | 73 | ### 1. Configure Conan 74 | 75 | ```bash 76 | pip install conan --upgrade 77 | conan config install https://github.com/ultimaker/conan-config.git 78 | conan profile new default --detect --force 79 | ``` 80 | 81 | Community developers would have to remove the Conan cura repository because it requires credentials. 82 | 83 | Ultimaker developers need to request an account for our JFrog Artifactory server at IT 84 | ```bash 85 | conan remote remove cura 86 | ``` 87 | 88 | ### 2. Clone libnest2d 89 | ```bash 90 | git clone https://github.com/Ultimaker/libnest2d.git 91 | cd libnest2d 92 | ``` 93 | 94 | ### 3. Install & Build libnest2d (Release OR Debug) 95 | 96 | #### Release 97 | ```bash 98 | conan install . --build=missing --update 99 | # optional for a specific version: conan install . libnest2d/@/ --build=missing --update 100 | cmake --preset release 101 | cmake --build --preset release 102 | ``` 103 | 104 | #### Debug 105 | 106 | ```bash 107 | conan install . --build=missing --update build_type=Debug 108 | cmake --preset debug 109 | cmake --build --preset debug 110 | ``` 111 | 112 | ## Creating a new libnest2d Conan package 113 | 114 | To create a new libnest2d Conan package such that it can be used in Cura and Uranium, run the following command: 115 | 116 | ```shell 117 | conan create . nest2d/@/ --build=missing --update 118 | ``` 119 | 120 | This package will be stored in the local Conan cache (`~/.conan/data` or `C:\Users\username\.conan\data` ) and can be used in downstream 121 | projects, such as Cura and Uranium by adding it as a requirement in the `conanfile.py` or in `conandata.yml`. 122 | 123 | Note: Make sure that the used `` is present in the conandata.yml in the libnest2d root 124 | 125 | You can also specify the override at the commandline, to use the newly created package, when you execute the `conan install` 126 | command in the root of the consuming project, with: 127 | 128 | 129 | ```shell 130 | conan install . -build=missing --update --require-override=libnest2d/@/ 131 | ``` 132 | 133 | ## Developing libnest2d In Editable Mode 134 | 135 | You can use your local development repository downsteam by adding it as an editable mode package. 136 | This means you can test this in a consuming project without creating a new package for this project every time. 137 | 138 | ```bash 139 | conan editable add . libnest2d/@/ 140 | ``` 141 | 142 | Then in your downsteam projects (Cura) root directory override the package with your editable mode package. 143 | 144 | ```shell 145 | conan install . -build=missing --update --require-override=libnest2d/@/ 146 | ``` 147 | 148 | # Example 149 | 150 | A simple example may be the best way to demonstrate the usage of the library. 151 | 152 | ``` c++ 153 | #include 154 | #include 155 | 156 | // Here we include the libnest2d library 157 | #include 158 | 159 | int main(int argc, const char* argv[]) { 160 | using namespace libnest2d; 161 | 162 | // Example polygons 163 | std::vector input1(23, 164 | { 165 | {-5000000, 8954050}, 166 | {5000000, 8954050}, 167 | {5000000, -45949}, 168 | {4972609, -568550}, 169 | {3500000, -8954050}, 170 | {-3500000, -8954050}, 171 | {-4972609, -568550}, 172 | {-5000000, -45949}, 173 | {-5000000, 8954050}, 174 | }); 175 | std::vector input2(15, 176 | { 177 | {-11750000, 13057900}, 178 | {-9807860, 15000000}, 179 | {4392139, 24000000}, 180 | {11750000, 24000000}, 181 | {11750000, -24000000}, 182 | {4392139, -24000000}, 183 | {-9807860, -15000000}, 184 | {-11750000, -13057900}, 185 | {-11750000, 13057900}, 186 | }); 187 | 188 | std::vector input; 189 | input.insert(input.end(), input1.begin(), input1.end()); 190 | input.insert(input.end(), input2.begin(), input2.end()); 191 | 192 | // Perform the nesting with a box shaped bin 193 | size_t bins = nest(input, Box(150000000, 150000000)); 194 | 195 | // Retrieve resulting geometries 196 | for(Item& r : input) { 197 | auto polygon = r.transformedShape(); 198 | // render polygon... 199 | } 200 | 201 | return EXIT_SUCCESS; 202 | } 203 | ``` 204 | 205 | It is worth to note that the type of the polygon carried by the Item objects is the type defined as a polygon by the geometry backend. In 206 | the example we use the clipper backend and clipper works with integer coordinates. 207 | 208 | Ofcourse it is possible to configure the nesting in every possible way. The ```nest``` function can take placer and selection algorithms as 209 | template arguments and their configuration as runtime arguments. It is also possible to pass a progress indication functor and a stop 210 | condition predicate to control the nesting process. For more details see the ```libnest2d.h``` header file. 211 | 212 | ## Example output 213 | 214 | ![Alt text](doc/img/example.svg) 215 | 216 | ## Screenshot from Slic3r PE 217 | 218 | For the record, **Slic3r PE** version 2.0 is now known as **PrusaSlicer 2.0**. 219 | 220 | ![Alt text](doc/img/slic3r_screenshot.png) 221 | 222 | # References 223 | 224 | - [SVGNest](https://github.com/Jack000/SVGnest) 225 | - [An effective heuristic for the two-dimensional irregular 226 | bin packing problem](http://www.cs.stir.ac.uk/~goc/papers/EffectiveHueristic2DAOR2013.pdf) 227 | - [Complete and robust no-fit polygon generation for the irregular stock cutting problem](https://www.sciencedirect.com/science/article/abs/pii/S0377221706001639) 228 | - [Applying Meta-Heuristic Algorithms to the Nesting 229 | Problem Utilising the No Fit Polygon](http://www.graham-kendall.com/papers/k2001.pdf) 230 | - [A comprehensive and robust procedure for obtaining the nofit polygon 231 | using Minkowski sums](https://www.sciencedirect.com/science/article/pii/S0305054806000669) 232 | -------------------------------------------------------------------------------- /conandata.yml: -------------------------------------------------------------------------------- 1 | version: "5.10.0" 2 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from os import path 4 | 5 | from conan import ConanFile 6 | from conan.errors import ConanInvalidConfiguration 7 | from conan.tools.cmake import CMakeToolchain, CMakeDeps, CMake, cmake_layout 8 | from conan.tools.files import AutoPackager, files, collect_libs, copy, update_conandata 9 | from conan.tools.build import check_min_cppstd 10 | from conan.tools.microsoft import check_min_vs, is_msvc 11 | from conan.tools.scm import Version, Git 12 | 13 | required_conan_version = ">=2.7.0" 14 | 15 | 16 | class Nest2DConan(ConanFile): 17 | name = "nest2d" 18 | description = "2D irregular bin packaging and nesting library written in modern C++" 19 | topics = ("conan", "cura", "prusaslicer", "nesting", "c++", "bin packaging") 20 | settings = "os", "compiler", "build_type", "arch" 21 | build_policy = "missing" 22 | package_type = "library" 23 | implements = ["auto_header_only"] 24 | 25 | options = { 26 | "shared": [True, False], 27 | "fPIC": [True, False], 28 | "header_only": [True, False], 29 | "geometries": ["clipper", "boost"], 30 | "optimizer": ["nlopt", "optimlib"], 31 | "threading": ["std", "tbb", "omp", "none"] 32 | } 33 | default_options = { 34 | "shared": True, 35 | "fPIC": True, 36 | "header_only": False, 37 | "geometries": "clipper", 38 | "optimizer": "nlopt", 39 | "threading": "std" 40 | } 41 | 42 | def set_version(self): 43 | if not self.version: 44 | self.version = self.conan_data["version"] 45 | 46 | def export(self): 47 | git = Git(self) 48 | update_conandata(self, {"version": self.version, "commit": git.get_commit()}) 49 | 50 | @property 51 | def _min_cppstd(self): 52 | return 17 53 | 54 | @property 55 | def _compilers_minimum_version(self): 56 | return { 57 | "gcc": "9", 58 | "clang": "9", 59 | "apple-clang": "9", 60 | "msvc": "192", 61 | "visual_studio": "14", 62 | } 63 | 64 | def export_sources(self): 65 | copy(self, "CMakeLists.txt", self.recipe_folder, self.export_sources_folder) 66 | copy(self, "*", path.join(self.recipe_folder, "src"), path.join(self.export_sources_folder, "src")) 67 | copy(self, "*", path.join(self.recipe_folder, "include"), path.join(self.export_sources_folder, "include")) 68 | copy(self, "*", path.join(self.recipe_folder, "tests"), path.join(self.export_sources_folder, "tests")) 69 | copy(self, "*", path.join(self.recipe_folder, "tools"), path.join(self.export_sources_folder, "tools")) 70 | 71 | def layout(self): 72 | cmake_layout(self) 73 | self.cpp.package.libs = ["nest2d"] 74 | 75 | def requirements(self): 76 | if self.options.geometries == "clipper": 77 | self.requires("clipper/6.4.2@ultimaker/stable", transitive_headers=True) 78 | if self.options.geometries == "boost" or self.options.geometries == "clipper": 79 | self.requires("boost/1.86.0", transitive_headers=True) 80 | if self.options.optimizer == "nlopt": 81 | self.requires("nlopt/2.7.1", transitive_headers=True) 82 | if self.options.optimizer == "optimlib": 83 | self.requires("armadillo/12.6.4", transitive_headers=True) 84 | if self.options.threading == "tbb": 85 | self.requires("tbb/2020.3", transitive_headers=True) 86 | if self.options.threading == "omp": 87 | self.requires("llvm-openmp/17.0.6", transitive_headers=True) 88 | 89 | def validate(self): 90 | if self.settings.compiler.cppstd: 91 | check_min_cppstd(self, self._min_cppstd) 92 | check_min_vs(self, 192) # TODO: remove in Conan 2.0 93 | if not is_msvc(self): 94 | minimum_version = self._compilers_minimum_version.get(str(self.settings.compiler), False) 95 | if minimum_version and Version(self.settings.compiler.version) < minimum_version: 96 | raise ConanInvalidConfiguration( 97 | f"{self.ref} requires C++{self._min_cppstd}, which your compiler does not support." 98 | ) 99 | 100 | def build_requirements(self): 101 | self.test_requires("standardprojectsettings/[>=0.2.0]") 102 | if not self.conf.get("tools.build:skip_test", False, check_type=bool): 103 | self.test_requires("catch2/[>=3.5.2]") 104 | 105 | def config_options(self): 106 | if self.settings.os == "Windows": 107 | del self.options.fPIC 108 | 109 | def configure(self): 110 | if self.options.shared: 111 | self.options.rm_safe("fPIC") 112 | self.options["boost"].header_only = True 113 | if self.options.geometries == "clipper": 114 | self.options["clipper"].shared = True if self.options.header_only else self.options.shared 115 | self.options[str(self.options.optimizer)].shared = True if self.options.header_only else self.options.shared 116 | if self.options.threading == "tbb": 117 | self.options["tbb"].shared = True if self.options.header_only else self.options.shared 118 | if self.options.threading == "omp": 119 | self.options["llvm-openmp"].shared = True if self.options.header_only else self.options.shared 120 | 121 | def generate(self): 122 | deps = CMakeDeps(self) 123 | if not self.conf.get("tools.build:skip_test", False, check_type=bool): 124 | deps.build_context_activated = ["catch2"] 125 | deps.build_context_build_modules = ["catch2"] 126 | deps.generate() 127 | 128 | tc = CMakeToolchain(self) 129 | tc.variables["HEADER_ONLY"] = self.options.header_only 130 | if not self.options.header_only: 131 | tc.variables["BUILD_SHARED_LIBS"] = self.options.shared 132 | tc.variables["ENABLE_TESTING"] = not self.conf.get("tools.build:skip_test", False, check_type=bool) 133 | tc.variables["GEOMETRIES"] = self.options.geometries 134 | tc.variables["OPTIMIZER"] = self.options.optimizer 135 | tc.variables["THREADING"] = self.options.threading 136 | 137 | tc.generate() 138 | 139 | def build(self): 140 | cmake = CMake(self) 141 | cmake.configure() 142 | cmake.build() 143 | cmake.install() 144 | 145 | def package(self): 146 | packager = AutoPackager(self) 147 | packager.run() 148 | 149 | # Remove the header files from options not used in this package 150 | if self.options.geometries != "clipper": 151 | files.rmdir(self, os.path.join(self.package_folder, "include", "libnest2d", "backends", "clipper")) 152 | if self.options.optimizer != "nlopt": 153 | files.rmdir(self, os.path.join(self.package_folder, "include", "libnest2d", "optimizers", "nlopt")) 154 | if self.options.optimizer != "optimlib": 155 | files.rmdir(self, os.path.join(self.package_folder, "include", "libnest2d", "optimizers", "optimlib")) 156 | 157 | def package_info(self): 158 | if not self.options.header_only: 159 | self.cpp_info.libs = collect_libs(self) 160 | self.cpp_info.defines.append(f"LIBNEST2D_GEOMETRIES_{self.options.geometries}") 161 | self.cpp_info.defines.append(f"LIBNEST2D_OPTIMIZERS_{self.options.optimizer}") 162 | self.cpp_info.defines.append(f"LIBNEST2D_THREADING_{self.options.threading}") 163 | if self.settings.os in ["Linux", "FreeBSD", "Macos"] and self.options.threading == "std": 164 | self.cpp_info.system_libs.append("pthread") 165 | -------------------------------------------------------------------------------- /doc/img/example.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 51 | 55 | 62 | 63 | 67 | 71 | 76 | 77 | 81 | 86 | 87 | 91 | 96 | 97 | 101 | 106 | 107 | 111 | 116 | 117 | 121 | 126 | 127 | 131 | 136 | 137 | 141 | 146 | 147 | 151 | 156 | 157 | 161 | 166 | 167 | 171 | 176 | 177 | 181 | 186 | 187 | 191 | 196 | 197 | 201 | 206 | 207 | 211 | 216 | 217 | 221 | 226 | 227 | 231 | 236 | 237 | 241 | 246 | 247 | 251 | 256 | 257 | 261 | 266 | 267 | 271 | 276 | 277 | 281 | 286 | 287 | 291 | 296 | 297 | 301 | 306 | 307 | 311 | 316 | 317 | 321 | 326 | 327 | 331 | 336 | 337 | 341 | 346 | 347 | 351 | 356 | 357 | 361 | 366 | 367 | 371 | 376 | 377 | 381 | 386 | 387 | 391 | 396 | 397 | 401 | 406 | 407 | 411 | 416 | 417 | 421 | 426 | 427 | 431 | 436 | 437 | 441 | 446 | 447 | 448 | 449 | -------------------------------------------------------------------------------- /doc/img/slic3r_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ultimaker/libnest2d/67ac07e9d41ba6c3146654684bac69f637120024/doc/img/slic3r_screenshot.png -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | project(Libnest2D_example) 4 | 5 | message(STATUS "PREFIX PATH = ${CMAKE_PREFIX_PATH}") 6 | find_package(Libnest2D QUIET) 7 | 8 | if(NOT Libnest2D_FOUND) 9 | message(STATUS "No installed version of Libnest2D has been found in the system. Falling back to subproject mode...") 10 | add_subdirectory(${PROJECT_SOURCE_DIR}/../ ${CMAKE_BINARY_DIR}/Libnest2D-build) 11 | add_library(Libnest2D::libnest2d ALIAS libnest2d) 12 | else() 13 | message(STATUS "Libnest2D was found in ${Libnest2D_DIR}") 14 | endif() 15 | 16 | add_executable(example ${PROJECT_SOURCE_DIR}/main.cpp 17 | ${PROJECT_SOURCE_DIR}/../tools/svgtools.hpp 18 | ${PROJECT_SOURCE_DIR}/../tools/printer_parts.cpp 19 | ${PROJECT_SOURCE_DIR}/../tools/printer_parts.hpp 20 | ) 21 | 22 | target_link_libraries(example Libnest2D::libnest2d) 23 | -------------------------------------------------------------------------------- /examples/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "../tools/printer_parts.hpp" 9 | #include "../tools/svgtools.hpp" 10 | 11 | using namespace libnest2d; 12 | 13 | static const std::vector& _parts(std::vector& ret, const TestData& data) 14 | { 15 | if(ret.empty()) { 16 | ret.reserve(data.size()); 17 | for(auto& inp : data) 18 | ret.emplace_back(inp); 19 | } 20 | 21 | return ret; 22 | } 23 | 24 | static const std::vector& prusaParts() { 25 | static std::vector ret; 26 | return _parts(ret, PRINTER_PART_POLYGONS); 27 | } 28 | 29 | int main(void /*int argc, char **argv*/) { 30 | 31 | std::vector input = prusaParts(); 32 | 33 | size_t bins = libnest2d::nest(input, Box(mm(250), mm(210)), 0, {}, 34 | ProgressFunction{[](unsigned cnt) { 35 | std::cout << "parts left: " << cnt << std::endl; 36 | }}); 37 | 38 | PackGroup pgrp(bins); 39 | 40 | for (Item &itm : input) { 41 | if (itm.binId() >= 0) pgrp[size_t(itm.binId())].emplace_back(itm); 42 | } 43 | 44 | using SVGWriter = libnest2d::svg::SVGWriter; 45 | SVGWriter::Config conf; 46 | conf.mm_in_coord_units = mm(); 47 | SVGWriter svgw(conf); 48 | svgw.setSize(Box(mm(250), mm(210))); 49 | svgw.writePackGroup(pgrp); 50 | svgw.save("out"); 51 | 52 | return EXIT_SUCCESS; 53 | } 54 | -------------------------------------------------------------------------------- /include/libnest2d/backends/clipper/clipper_polygon.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CLIPPER_POLYGON_HPP 2 | #define CLIPPER_POLYGON_HPP 3 | 4 | #include 5 | 6 | namespace ClipperLib { 7 | 8 | struct Polygon { 9 | Path Contour; 10 | Paths Holes; 11 | 12 | inline Polygon() = default; 13 | 14 | inline explicit Polygon(const Path& cont): Contour(cont) {} 15 | // inline explicit Polygon(const Paths& holes): 16 | // Holes(holes) {} 17 | inline Polygon(const Path& cont, const Paths& holes): 18 | Contour(cont), Holes(holes) {} 19 | 20 | inline explicit Polygon(Path&& cont): Contour(std::move(cont)) {} 21 | // inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {} 22 | inline Polygon(Path&& cont, Paths&& holes): 23 | Contour(std::move(cont)), Holes(std::move(holes)) {} 24 | }; 25 | 26 | inline IntPoint& operator +=(IntPoint& p, const IntPoint& pa ) { 27 | // This could be done with SIMD 28 | p.X += pa.X; 29 | p.Y += pa.Y; 30 | return p; 31 | } 32 | 33 | inline IntPoint operator+(const IntPoint& p1, const IntPoint& p2) { 34 | IntPoint ret = p1; 35 | ret += p2; 36 | return ret; 37 | } 38 | 39 | inline IntPoint& operator -=(IntPoint& p, const IntPoint& pa ) { 40 | p.X -= pa.X; 41 | p.Y -= pa.Y; 42 | return p; 43 | } 44 | 45 | inline IntPoint operator -(const IntPoint& p ) { 46 | IntPoint ret = p; 47 | ret.X = -ret.X; 48 | ret.Y = -ret.Y; 49 | return ret; 50 | } 51 | 52 | inline IntPoint operator-(const IntPoint& p1, const IntPoint& p2) { 53 | IntPoint ret = p1; 54 | ret -= p2; 55 | return ret; 56 | } 57 | 58 | inline IntPoint& operator *=(IntPoint& p, const IntPoint& pa ) { 59 | p.X *= pa.X; 60 | p.Y *= pa.Y; 61 | return p; 62 | } 63 | 64 | inline IntPoint operator*(const IntPoint& p1, const IntPoint& p2) { 65 | IntPoint ret = p1; 66 | ret *= p2; 67 | return ret; 68 | } 69 | 70 | } 71 | 72 | #endif // CLIPPER_POLYGON_HPP 73 | -------------------------------------------------------------------------------- /include/libnest2d/backends/clipper/geometries.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CLIPPER_BACKEND_HPP 2 | #define CLIPPER_BACKEND_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include "clipper_polygon.hpp" 14 | 15 | namespace libnest2d { 16 | 17 | // Aliases for convinience 18 | using PointImpl = ClipperLib::IntPoint; 19 | using PathImpl = ClipperLib::Path; 20 | using HoleStore = ClipperLib::Paths; 21 | using PolygonImpl = ClipperLib::Polygon; 22 | 23 | template<> struct ShapeTag { using Type = PolygonTag; }; 24 | template<> struct ShapeTag { using Type = PathTag; }; 25 | template<> struct ShapeTag { using Type = PointTag; }; 26 | 27 | // Type of coordinate units used by Clipper. Enough to specialize for point, 28 | // the rest of the types will work (Path, Polygon) 29 | template<> struct CoordType { 30 | using Type = ClipperLib::cInt; 31 | static const constexpr ClipperLib::cInt MM_IN_COORDS = 1000000; 32 | }; 33 | 34 | // Enough to specialize for path, it will work for multishape and Polygon 35 | template<> struct PointType { using Type = PointImpl; }; 36 | 37 | // This is crucial. CountourType refers to itself by default, so we don't have 38 | // to secialize for clipper Path. ContourType::Type is PathImpl. 39 | template<> struct ContourType { using Type = PathImpl; }; 40 | 41 | // The holes are contained in Clipper::Paths 42 | template<> struct HolesContainer { using Type = ClipperLib::Paths; }; 43 | 44 | namespace pointlike { 45 | 46 | // Tell libnest2d how to extract the X coord from a ClipperPoint object 47 | template<> inline ClipperLib::cInt x(const PointImpl& p) 48 | { 49 | return p.X; 50 | } 51 | 52 | // Tell libnest2d how to extract the Y coord from a ClipperPoint object 53 | template<> inline ClipperLib::cInt y(const PointImpl& p) 54 | { 55 | return p.Y; 56 | } 57 | 58 | // Tell libnest2d how to extract the X coord from a ClipperPoint object 59 | template<> inline ClipperLib::cInt& x(PointImpl& p) 60 | { 61 | return p.X; 62 | } 63 | 64 | // Tell libnest2d how to extract the Y coord from a ClipperPoint object 65 | template<> inline ClipperLib::cInt& y(PointImpl& p) 66 | { 67 | return p.Y; 68 | } 69 | 70 | } 71 | 72 | // Using the libnest2d default area implementation 73 | #define DISABLE_BOOST_AREA 74 | 75 | namespace shapelike { 76 | 77 | template<> 78 | inline void offset(PolygonImpl& sh, TCoord distance, const PolygonTag&) 79 | { 80 | #define DISABLE_BOOST_OFFSET 81 | 82 | using ClipperLib::ClipperOffset; 83 | using ClipperLib::jtMiter; 84 | using ClipperLib::etClosedPolygon; 85 | using ClipperLib::Paths; 86 | 87 | Paths result; 88 | 89 | try { 90 | ClipperOffset offs; 91 | offs.AddPath(sh.Contour, jtMiter, etClosedPolygon); 92 | offs.AddPaths(sh.Holes, jtMiter, etClosedPolygon); 93 | offs.Execute(result, static_cast(distance)); 94 | } catch (ClipperLib::clipperException &) { 95 | throw GeometryException(GeomErr::OFFSET); 96 | } 97 | 98 | // Offsetting reverts the orientation and also removes the last vertex 99 | // so boost will not have a closed polygon. 100 | 101 | bool found_the_contour = false; 102 | for(auto& r : result) { 103 | if(ClipperLib::Orientation(r)) { 104 | // We don't like if the offsetting generates more than one contour 105 | // but throwing would be an overkill. Instead, we should warn the 106 | // caller about the inability to create correct geometries 107 | if(!found_the_contour) { 108 | sh.Contour = std::move(r); 109 | ClipperLib::ReversePath(sh.Contour); 110 | auto front_p = sh.Contour.front(); 111 | sh.Contour.emplace_back(std::move(front_p)); 112 | found_the_contour = true; 113 | } else { 114 | dout() << "Warning: offsetting result is invalid!"; 115 | /* TODO warning */ 116 | } 117 | } else { 118 | // TODO If there are multiple contours we can't be sure which hole 119 | // belongs to the first contour. (But in this case the situation is 120 | // bad enough to let it go...) 121 | sh.Holes.emplace_back(std::move(r)); 122 | ClipperLib::ReversePath(sh.Holes.back()); 123 | auto front_p = sh.Holes.back().front(); 124 | sh.Holes.back().emplace_back(std::move(front_p)); 125 | } 126 | } 127 | } 128 | 129 | template<> 130 | inline void offset(PathImpl& sh, TCoord distance, const PathTag&) 131 | { 132 | PolygonImpl p(std::move(sh)); 133 | offset(p, distance, PolygonTag()); 134 | sh = p.Contour; 135 | } 136 | 137 | // Tell libnest2d how to make string out of a ClipperPolygon object 138 | template<> inline std::string toString(const PolygonImpl& sh) 139 | { 140 | std::stringstream ss; 141 | 142 | ss << "Contour {\n"; 143 | for(auto p : sh.Contour) { 144 | ss << "\t" << p.X << " " << p.Y << "\n"; 145 | } 146 | ss << "}\n"; 147 | 148 | for(auto& h : sh.Holes) { 149 | ss << "Holes {\n"; 150 | for(auto p : h) { 151 | ss << "\t{\n"; 152 | ss << "\t\t" << p.X << " " << p.Y << "\n"; 153 | ss << "\t}\n"; 154 | } 155 | ss << "}\n"; 156 | } 157 | 158 | return ss.str(); 159 | } 160 | 161 | template<> 162 | inline PolygonImpl create(const PathImpl& path, const HoleStore& holes) 163 | { 164 | PolygonImpl p; 165 | p.Contour = path; 166 | p.Holes = holes; 167 | 168 | return p; 169 | } 170 | 171 | template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) { 172 | PolygonImpl p; 173 | p.Contour.swap(path); 174 | p.Holes.swap(holes); 175 | 176 | return p; 177 | } 178 | 179 | template<> 180 | inline const THolesContainer& holes(const PolygonImpl& sh) 181 | { 182 | return sh.Holes; 183 | } 184 | 185 | template<> inline THolesContainer& holes(PolygonImpl& sh) 186 | { 187 | return sh.Holes; 188 | } 189 | 190 | template<> 191 | inline TContour& hole(PolygonImpl& sh, unsigned long idx) 192 | { 193 | return sh.Holes[idx]; 194 | } 195 | 196 | template<> 197 | inline const TContour& hole(const PolygonImpl& sh, 198 | unsigned long idx) 199 | { 200 | return sh.Holes[idx]; 201 | } 202 | 203 | template<> inline size_t holeCount(const PolygonImpl& sh) 204 | { 205 | return sh.Holes.size(); 206 | } 207 | 208 | template<> inline PathImpl& contour(PolygonImpl& sh) 209 | { 210 | return sh.Contour; 211 | } 212 | 213 | template<> 214 | inline const PathImpl& contour(const PolygonImpl& sh) 215 | { 216 | return sh.Contour; 217 | } 218 | 219 | #define DISABLE_BOOST_TRANSLATE 220 | template<> 221 | inline void translate(PolygonImpl& sh, const PointImpl& offs) 222 | { 223 | for(auto& p : sh.Contour) { p += offs; } 224 | for(auto& hole : sh.Holes) for(auto& p : hole) { p += offs; } 225 | } 226 | 227 | #define DISABLE_BOOST_ROTATE 228 | template<> 229 | inline void rotate(PolygonImpl& sh, const Radians& rads) 230 | { 231 | using Coord = TCoord; 232 | 233 | auto cosa = rads.cos(); 234 | auto sina = rads.sin(); 235 | 236 | for(auto& p : sh.Contour) { 237 | p = { 238 | static_cast(p.X * cosa - p.Y * sina), 239 | static_cast(p.X * sina + p.Y * cosa) 240 | }; 241 | } 242 | for(auto& hole : sh.Holes) for(auto& p : hole) { 243 | p = { 244 | static_cast(p.X * cosa - p.Y * sina), 245 | static_cast(p.X * sina + p.Y * cosa) 246 | }; 247 | } 248 | } 249 | 250 | } // namespace shapelike 251 | 252 | #define DISABLE_BOOST_NFP_MERGE 253 | inline TMultiShape clipper_execute( 254 | ClipperLib::Clipper& clipper, 255 | ClipperLib::ClipType clipType, 256 | ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd, 257 | ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd) 258 | { 259 | TMultiShape retv; 260 | 261 | ClipperLib::PolyTree result; 262 | clipper.Execute(clipType, result, subjFillType, clipFillType); 263 | 264 | retv.reserve(static_cast(result.Total())); 265 | 266 | std::function processHole; 267 | 268 | auto processPoly = [&retv, &processHole](ClipperLib::PolyNode *pptr) { 269 | PolygonImpl poly; 270 | poly.Contour.swap(pptr->Contour); 271 | 272 | assert(!pptr->IsHole()); 273 | 274 | if(!poly.Contour.empty() ) { 275 | auto front_p = poly.Contour.front(); 276 | auto &back_p = poly.Contour.back(); 277 | if(front_p.X != back_p.X || front_p.Y != back_p.X) 278 | poly.Contour.emplace_back(front_p); 279 | } 280 | 281 | for(auto h : pptr->Childs) { processHole(h, poly); } 282 | retv.push_back(poly); 283 | }; 284 | 285 | processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly) 286 | { 287 | poly.Holes.emplace_back(std::move(pptr->Contour)); 288 | 289 | assert(pptr->IsHole()); 290 | 291 | if(!poly.Contour.empty() ) { 292 | auto front_p = poly.Contour.front(); 293 | auto &back_p = poly.Contour.back(); 294 | if(front_p.X != back_p.X || front_p.Y != back_p.X) 295 | poly.Contour.emplace_back(front_p); 296 | } 297 | 298 | for(auto c : pptr->Childs) processPoly(c); 299 | }; 300 | 301 | auto traverse = [&processPoly] (ClipperLib::PolyNode *node) 302 | { 303 | for(auto ch : node->Childs) processPoly(ch); 304 | }; 305 | 306 | traverse(&result); 307 | 308 | return retv; 309 | } 310 | 311 | namespace nfp { 312 | 313 | template<> inline TMultiShape 314 | merge(const TMultiShape& shapes) 315 | { 316 | ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); 317 | 318 | bool closed = true; 319 | bool valid = true; 320 | 321 | for(auto& path : shapes) { 322 | valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); 323 | 324 | for(auto& h : path.Holes) 325 | valid &= clipper.AddPath(h, ClipperLib::ptSubject, closed); 326 | } 327 | 328 | if(!valid) throw GeometryException(GeomErr::MERGE); 329 | 330 | return clipper_execute(clipper, ClipperLib::ctUnion, ClipperLib::pftNegative); 331 | } 332 | 333 | } 334 | 335 | } 336 | 337 | #define DISABLE_BOOST_CONVEX_HULL 338 | 339 | //#define DISABLE_BOOST_SERIALIZE 340 | //#define DISABLE_BOOST_UNSERIALIZE 341 | 342 | #ifdef _MSC_VER 343 | #pragma warning(push) 344 | #pragma warning(disable: 4244) 345 | #pragma warning(disable: 4267) 346 | #endif 347 | // All other operators and algorithms are implemented with boost 348 | #include 349 | #ifdef _MSC_VER 350 | #pragma warning(pop) 351 | #endif 352 | 353 | #endif // CLIPPER_BACKEND_HPP 354 | -------------------------------------------------------------------------------- /include/libnest2d/common.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIBNEST2D_CONFIG_HPP 2 | #define LIBNEST2D_CONFIG_HPP 3 | 4 | #ifndef NDEBUG 5 | #include 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L 15 | #define BP2D_NOEXCEPT 16 | #define BP2D_CONSTEXPR 17 | #define BP2D_COMPILER_MSVC12 18 | #elif __cplusplus >= 201103L 19 | #define BP2D_NOEXCEPT noexcept 20 | #define BP2D_CONSTEXPR constexpr 21 | #endif 22 | 23 | /* 24 | * Debugging output dout and derr definition 25 | */ 26 | //#ifndef NDEBUG 27 | //# define dout std::cout 28 | //# define derr std::cerr 29 | //#else 30 | //# define dout 0 && std::cout 31 | //# define derr 0 && std::cerr 32 | //#endif 33 | 34 | namespace libnest2d { 35 | 36 | struct DOut { 37 | #ifndef NDEBUG 38 | std::ostream& out = std::cout; 39 | #endif 40 | }; 41 | 42 | struct DErr { 43 | #ifndef NDEBUG 44 | std::ostream& out = std::cerr; 45 | #endif 46 | }; 47 | 48 | template 49 | inline DOut&& operator<<( DOut&& out, T&& d) { 50 | #ifndef NDEBUG 51 | out.out << d; 52 | #endif 53 | return std::move(out); 54 | } 55 | 56 | template 57 | inline DErr&& operator<<( DErr&& out, T&& d) { 58 | #ifndef NDEBUG 59 | out.out << d; 60 | #endif 61 | return std::move(out); 62 | } 63 | inline DOut dout() { return DOut(); } 64 | inline DErr derr() { return DErr(); } 65 | 66 | template< class T > 67 | struct remove_cvref { 68 | using type = typename std::remove_cv< 69 | typename std::remove_reference::type>::type; 70 | }; 71 | 72 | template< class T > 73 | using remove_cvref_t = typename remove_cvref::type; 74 | 75 | template< class T > 76 | using remove_ref_t = typename std::remove_reference::type; 77 | 78 | template 79 | using enable_if_t = typename std::enable_if::type; 80 | 81 | template 82 | struct invoke_result { 83 | using type = typename std::result_of::type; 84 | }; 85 | 86 | template 87 | using invoke_result_t = typename invoke_result::type; 88 | 89 | /** 90 | * A useful little tool for triggering static_assert error messages e.g. when 91 | * a mandatory template specialization (implementation) is missing. 92 | * 93 | * \tparam T A template argument that may come from and outer template method. 94 | */ 95 | template struct always_false { enum { value = false }; }; 96 | 97 | const double BP2D_CONSTEXPR Pi = 3.141592653589793238463; // 2*std::acos(0); 98 | const double BP2D_CONSTEXPR Pi_2 = 2*Pi; 99 | 100 | /** 101 | * @brief Only for the Radian and Degrees classes to behave as doubles. 102 | */ 103 | class Double { 104 | protected: 105 | double val_; 106 | public: 107 | Double(): val_(double{}) { } 108 | Double(double d) : val_(d) { } 109 | 110 | operator double() const BP2D_NOEXCEPT { return val_; } 111 | operator double&() BP2D_NOEXCEPT { return val_; } 112 | }; 113 | 114 | class Degrees; 115 | 116 | /** 117 | * @brief Data type representing radians. It supports conversion to degrees. 118 | */ 119 | class Radians: public Double { 120 | mutable double sin_ = std::nan(""), cos_ = std::nan(""); 121 | public: 122 | Radians(double rads = Double() ): Double(rads) {} 123 | inline Radians(const Degrees& degs); 124 | 125 | inline operator Degrees(); 126 | inline double toDegrees(); 127 | 128 | inline double sin() const { 129 | if(std::isnan(sin_)) { 130 | cos_ = std::cos(val_); 131 | sin_ = std::sin(val_); 132 | } 133 | return sin_; 134 | } 135 | 136 | inline double cos() const { 137 | if(std::isnan(cos_)) { 138 | cos_ = std::cos(val_); 139 | sin_ = std::sin(val_); 140 | } 141 | return cos_; 142 | } 143 | }; 144 | 145 | /** 146 | * @brief Data type representing degrees. It supports conversion to radians. 147 | */ 148 | class Degrees: public Double { 149 | public: 150 | Degrees(double deg = Double()): Double(deg) {} 151 | Degrees(const Radians& rads): Double( rads * 180/Pi ) {} 152 | inline double toRadians() { return Radians(*this);} 153 | }; 154 | 155 | inline bool operator==(const Degrees& deg, const Radians& rads) { 156 | Degrees deg2 = rads; 157 | auto diff = std::abs(deg - deg2); 158 | return diff < 0.0001; 159 | } 160 | 161 | inline bool operator==(const Radians& rads, const Degrees& deg) { 162 | return deg == rads; 163 | } 164 | 165 | inline Radians::operator Degrees() { return *this * 180/Pi; } 166 | 167 | inline Radians::Radians(const Degrees °s): Double( degs * Pi/180) {} 168 | 169 | inline double Radians::toDegrees() { return operator Degrees(); } 170 | 171 | enum class GeomErr : std::size_t { 172 | OFFSET, 173 | MERGE, 174 | NFP 175 | }; 176 | 177 | const std::string ERROR_STR[] = { 178 | "Offsetting could not be done! An invalid geometry may have been added.", 179 | "Error while merging geometries!", 180 | "No fit polygon cannot be calculated." 181 | }; 182 | 183 | class GeometryException: public std::exception { 184 | 185 | virtual const std::string& errorstr(GeomErr errcode) const BP2D_NOEXCEPT { 186 | return ERROR_STR[static_cast(errcode)]; 187 | } 188 | 189 | GeomErr errcode_; 190 | public: 191 | 192 | GeometryException(GeomErr code): errcode_(code) {} 193 | 194 | GeomErr errcode() const { return errcode_; } 195 | 196 | const char * what() const BP2D_NOEXCEPT override { 197 | return errorstr(errcode_).c_str(); 198 | } 199 | }; 200 | 201 | struct ScalarTag {}; 202 | struct BigIntTag {}; 203 | struct RationalTag {}; 204 | 205 | template struct _NumTag { 206 | using Type = 207 | enable_if_t::value, ScalarTag>; 208 | }; 209 | 210 | template using NumTag = typename _NumTag>::Type; 211 | 212 | /// A local version for abs that is garanteed to work with libnest2d types 213 | template inline T abs(const T& v, ScalarTag) 214 | { 215 | return std::abs(v); 216 | } 217 | 218 | template inline T abs(const T& v) { return abs(v, NumTag()); } 219 | 220 | template inline T2 cast(const T1& v, ScalarTag, ScalarTag) 221 | { 222 | return static_cast(v); 223 | } 224 | 225 | template inline T2 cast(const T1& v) { 226 | return cast(v, NumTag(), NumTag()); 227 | } 228 | 229 | } 230 | #endif // LIBNEST2D_CONFIG_HPP 231 | -------------------------------------------------------------------------------- /include/libnest2d/libnest2d.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIBNEST2D_HPP 2 | #define LIBNEST2D_HPP 3 | 4 | // The type of backend should be set conditionally by the cmake configuriation 5 | // for now we set it statically to clipper backend 6 | #ifdef LIBNEST2D_GEOMETRIES_clipper 7 | #include 8 | #endif 9 | 10 | #ifdef LIBNEST2D_OPTIMIZER_nlopt 11 | // We include the stock optimizers for local and global optimization 12 | #include // Local subplex for NfpPlacer 13 | #include // Genetic for min. bounding box 14 | #endif 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace libnest2d { 24 | 25 | using Point = PointImpl; 26 | using Coord = TCoord; 27 | using Box = _Box; 28 | using Segment = _Segment; 29 | using Circle = _Circle; 30 | 31 | using Item = _Item; 32 | using Rectangle = _Rectangle; 33 | using PackGroup = _PackGroup; 34 | 35 | using FillerSelection = selections::_FillerSelection; 36 | using FirstFitSelection = selections::_FirstFitSelection; 37 | using DJDHeuristic = selections::_DJDHeuristic; 38 | 39 | template // Generic placer for arbitrary bin types 40 | using _NfpPlacer = placers::_NofitPolyPlacer; 41 | 42 | // NfpPlacer is with Box bin 43 | using NfpPlacer = _NfpPlacer; 44 | 45 | // This supports only box shaped bins 46 | using BottomLeftPlacer = placers::_BottomLeftPlacer; 47 | 48 | #ifdef LIBNEST2D_STATIC 49 | 50 | extern template class _Nester; 51 | extern template class _Nester; 52 | extern template std::size_t _Nester::execute( 53 | std::vector::iterator, std::vector::iterator); 54 | extern template std::size_t _Nester::execute( 55 | std::vector::iterator, std::vector::iterator); 56 | 57 | #endif 58 | 59 | template 60 | struct NestConfig { 61 | typename Placer::Config placer_config; 62 | typename Selector::Config selector_config; 63 | using Placement = typename Placer::Config; 64 | using Selection = typename Selector::Config; 65 | 66 | NestConfig() = default; 67 | NestConfig(const typename Placer::Config &cfg) : placer_config{cfg} {} 68 | NestConfig(const typename Selector::Config &cfg) : selector_config{cfg} {} 69 | NestConfig(const typename Placer::Config & pcfg, 70 | const typename Selector::Config &scfg) 71 | : placer_config{pcfg}, selector_config{scfg} {} 72 | }; 73 | 74 | struct NestControl { 75 | ProgressFunction progressfn; 76 | StopCondition stopcond = []{ return false; }; 77 | 78 | NestControl() = default; 79 | NestControl(ProgressFunction pr) : progressfn{std::move(pr)} {} 80 | NestControl(StopCondition sc) : stopcond{std::move(sc)} {} 81 | NestControl(ProgressFunction pr, StopCondition sc) 82 | : progressfn{std::move(pr)}, stopcond{std::move(sc)} 83 | {} 84 | }; 85 | 86 | template::iterator> 89 | std::size_t nest(Iterator from, Iterator to, 90 | const typename Placer::BinType & bin, 91 | Coord dist = 0, 92 | const NestConfig &cfg = {}, 93 | NestControl ctl = {}) 94 | { 95 | _Nester nester{bin, dist, cfg.placer_config, cfg.selector_config}; 96 | if(ctl.progressfn) nester.progressIndicator(ctl.progressfn); 97 | if(ctl.stopcond) nester.stopCondition(ctl.stopcond); 98 | return nester.execute(from, to); 99 | } 100 | 101 | #ifdef LIBNEST2D_STATIC 102 | 103 | extern template class _Nester; 104 | extern template class _Nester; 105 | extern template std::size_t nest(std::vector::iterator from, 106 | std::vector::iterator to, 107 | const Box & bin, 108 | Coord dist, 109 | const NestConfig &cfg, 110 | NestControl ctl); 111 | extern template std::size_t nest(std::vector::iterator from, 112 | std::vector::iterator to, 113 | const Box & bin, 114 | Coord dist, 115 | const NestConfig &cfg, 116 | NestControl ctl); 117 | 118 | #endif 119 | 120 | template> 123 | std::size_t nest(Container&& cont, 124 | const typename Placer::BinType & bin, 125 | Coord dist = 0, 126 | const NestConfig &cfg = {}, 127 | NestControl ctl = {}) 128 | { 129 | return nest(cont.begin(), cont.end(), bin, dist, cfg, ctl); 130 | } 131 | 132 | template enable_if_t::value, TCoord> mm(T val = T(1)) 133 | { 134 | return TCoord(val * CoordType::MM_IN_COORDS); 135 | } 136 | 137 | } 138 | 139 | #endif // LIBNEST2D_HPP 140 | -------------------------------------------------------------------------------- /include/libnest2d/optimizer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef OPTIMIZER_HPP 2 | #define OPTIMIZER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace libnest2d { namespace opt { 11 | 12 | using std::forward; 13 | using std::tuple; 14 | using std::make_tuple; 15 | 16 | /// A Type trait for upper and lower limit of a numeric type. 17 | template 18 | struct limits { 19 | inline static T min() { return std::numeric_limits::min(); } 20 | inline static T max() { return std::numeric_limits::max(); } 21 | }; 22 | 23 | template 24 | struct limits::has_infinity, void>> { 25 | inline static T min() { return -std::numeric_limits::infinity(); } 26 | inline static T max() { return std::numeric_limits::infinity(); } 27 | }; 28 | 29 | /// An interval of possible input values for optimization 30 | template 31 | class Bound { 32 | T min_; 33 | T max_; 34 | public: 35 | Bound(const T& min = limits::min(), 36 | const T& max = limits::max()): min_(min), max_(max) {} 37 | inline const T min() const BP2D_NOEXCEPT { return min_; } 38 | inline const T max() const BP2D_NOEXCEPT { return max_; } 39 | }; 40 | 41 | /** 42 | * Helper function to make a Bound object with its type deduced automatically. 43 | */ 44 | template 45 | inline Bound bound(const T& min, const T& max) { return Bound(min, max); } 46 | 47 | /** 48 | * This is the type of an input tuple for the object function. It holds the 49 | * values and their type in each dimension. 50 | */ 51 | template using Input = tuple; 52 | 53 | template 54 | inline tuple initvals(Args...args) { return make_tuple(args...); } 55 | 56 | /** 57 | * @brief Specific optimization methods for which a default optimizer 58 | * implementation can be instantiated. 59 | */ 60 | enum class Method { 61 | L_SIMPLEX, 62 | L_SUBPLEX, 63 | G_GENETIC, 64 | G_PARTICLE_SWARM 65 | //... 66 | }; 67 | 68 | /** 69 | * @brief Info about result of an optimization. These codes are exactly the same 70 | * as the nlopt codes for convinience. 71 | */ 72 | enum ResultCodes { 73 | FAILURE = -1, /* generic failure code */ 74 | INVALID_ARGS = -2, 75 | OUT_OF_MEMORY = -3, 76 | ROUNDOFF_LIMITED = -4, 77 | FORCED_STOP = -5, 78 | SUCCESS = 1, /* generic success code */ 79 | STOPVAL_REACHED = 2, 80 | FTOL_REACHED = 3, 81 | XTOL_REACHED = 4, 82 | MAXEVAL_REACHED = 5, 83 | MAXTIME_REACHED = 6 84 | }; 85 | 86 | /** 87 | * \brief A type to hold the complete result of the optimization. 88 | */ 89 | template 90 | struct Result { 91 | ResultCodes resultcode; 92 | tuple optimum; 93 | double score; 94 | }; 95 | 96 | /** 97 | * @brief A type for specifying the stop criteria. 98 | */ 99 | struct StopCriteria { 100 | 101 | /// If the absolute value difference between two scores. 102 | double absolute_score_difference = std::nan(""); 103 | 104 | /// If the relative value difference between two scores. 105 | double relative_score_difference = std::nan(""); 106 | 107 | /// Stop if this value or better is found. 108 | double stop_score = std::nan(""); 109 | 110 | /// A predicate that if evaluates to true, the optimization should terminate 111 | /// and the best result found prior to termination should be returned. 112 | std::function stop_condition = [] { return false; }; 113 | 114 | /// The max allowed number of iterations. 115 | unsigned max_iterations = 0; 116 | }; 117 | 118 | /** 119 | * \brief The Optimizer base class with CRTP pattern. 120 | */ 121 | template 122 | class Optimizer { 123 | protected: 124 | enum class OptDir{ 125 | MIN, 126 | MAX 127 | } dir_; 128 | 129 | StopCriteria stopcr_; 130 | 131 | public: 132 | 133 | inline explicit Optimizer(const StopCriteria& scr = {}): stopcr_(scr) {} 134 | 135 | /** 136 | * \brief Optimize for minimum value of the provided objectfunction. 137 | * \param objectfunction The function that will be searched for the minimum 138 | * return value. 139 | * \param initvals A tuple with the initial values for the search 140 | * \param bounds A parameter pack with the bounds for each dimension. 141 | * \return Returns a Result structure. 142 | * An example call would be: 143 | * auto result = opt.optimize_min( 144 | * [](tuple x) // object function 145 | * { 146 | * return std::pow(std::get<0>(x), 2); 147 | * }, 148 | * make_tuple(-0.5), // initial value 149 | * {-1.0, 1.0} // search space bounds 150 | * ); 151 | */ 152 | template 153 | inline Result optimize_min(Func&& objectfunction, 154 | Input initvals, 155 | Bound... bounds) 156 | { 157 | dir_ = OptDir::MIN; 158 | return static_cast(this)->template optimize( 159 | forward(objectfunction), initvals, bounds... ); 160 | } 161 | 162 | template 163 | inline Result optimize_min(Func&& objectfunction, 164 | Input initvals) 165 | { 166 | dir_ = OptDir::MIN; 167 | return static_cast(this)->template optimize( 168 | forward(objectfunction), initvals, Bound()... ); 169 | } 170 | 171 | template 172 | inline Result optimize_min(Func&& objectfunction) 173 | { 174 | dir_ = OptDir::MIN; 175 | return static_cast(this)->template optimize( 176 | forward(objectfunction), 177 | Input(), 178 | Bound()... ); 179 | } 180 | 181 | /// Same as optimize_min but optimizes for maximum function value. 182 | template 183 | inline Result optimize_max(Func&& objectfunction, 184 | Input initvals, 185 | Bound... bounds) 186 | { 187 | dir_ = OptDir::MAX; 188 | return static_cast(this)->template optimize( 189 | forward(objectfunction), initvals, bounds... ); 190 | } 191 | 192 | template 193 | inline Result optimize_max(Func&& objectfunction, 194 | Input initvals) 195 | { 196 | dir_ = OptDir::MAX; 197 | return static_cast(this)->template optimize( 198 | forward(objectfunction), initvals, Bound()... ); 199 | } 200 | 201 | template 202 | inline Result optimize_max(Func&& objectfunction) 203 | { 204 | dir_ = OptDir::MAX; 205 | return static_cast(this)->template optimize( 206 | forward(objectfunction), 207 | Input(), 208 | Bound()... ); 209 | } 210 | 211 | }; 212 | 213 | // Just to be able to instantiate an unimplemented method and generate compile 214 | // error. 215 | template 216 | class DummyOptimizer : public Optimizer> { 217 | friend class Optimizer>; 218 | 219 | public: 220 | DummyOptimizer() { 221 | static_assert(always_false::value, "Optimizer unimplemented!"); 222 | } 223 | 224 | DummyOptimizer(const StopCriteria&) { 225 | static_assert(always_false::value, "Optimizer unimplemented!"); 226 | } 227 | 228 | template 229 | Result optimize(Func&& /*func*/, 230 | tuple /*initvals*/, 231 | Bound... /*args*/) 232 | { 233 | return Result(); 234 | } 235 | }; 236 | 237 | // Specializing this struct will tell what kind of optimizer to generate for 238 | // a given method 239 | template struct OptimizerSubclass { using Type = DummyOptimizer<>; }; 240 | 241 | /// Optimizer type based on the method provided in parameter m. 242 | template using TOptimizer = typename OptimizerSubclass::Type; 243 | 244 | /// Global optimizer with an explicitly specified local method. 245 | template 246 | inline TOptimizer GlobalOptimizer(Method, const StopCriteria& scr = {}) 247 | { // Need to be specialized in order to do anything useful. 248 | return TOptimizer(scr); 249 | } 250 | 251 | } 252 | } 253 | 254 | #endif // OPTIMIZER_HPP 255 | -------------------------------------------------------------------------------- /include/libnest2d/optimizers/nlopt/genetic.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GENETIC_HPP 2 | #define GENETIC_HPP 3 | 4 | #include "nlopt_boilerplate.hpp" 5 | 6 | namespace libnest2d { namespace opt { 7 | 8 | class GeneticOptimizer: public NloptOptimizer { 9 | public: 10 | inline explicit GeneticOptimizer(const StopCriteria& scr = {}): 11 | NloptOptimizer(method2nloptAlg(Method::G_GENETIC), scr) {} 12 | 13 | inline GeneticOptimizer& localMethod(Method m) { 14 | localmethod_ = m; 15 | return *this; 16 | } 17 | 18 | inline void seed(unsigned long val) { nlopt::srand(val); } 19 | }; 20 | 21 | template<> 22 | struct OptimizerSubclass { using Type = GeneticOptimizer; }; 23 | 24 | template<> 25 | inline TOptimizer GlobalOptimizer( 26 | Method localm, const StopCriteria& scr ) 27 | { 28 | return GeneticOptimizer (scr).localMethod(localm); 29 | } 30 | 31 | } 32 | } 33 | 34 | #endif // GENETIC_HPP 35 | -------------------------------------------------------------------------------- /include/libnest2d/optimizers/nlopt/nlopt_boilerplate.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NLOPT_BOILERPLATE_HPP 2 | #define NLOPT_BOILERPLATE_HPP 3 | 4 | #ifdef _MSC_VER 5 | #pragma warning(push) 6 | #pragma warning(disable: 4244) 7 | #pragma warning(disable: 4267) 8 | #endif 9 | #include 10 | #ifdef _MSC_VER 11 | #pragma warning(pop) 12 | #endif 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | namespace libnest2d { namespace opt { 21 | 22 | inline nlopt::algorithm method2nloptAlg(Method m) { 23 | 24 | switch(m) { 25 | case Method::L_SIMPLEX: return nlopt::LN_NELDERMEAD; 26 | case Method::L_SUBPLEX: return nlopt::LN_SBPLX; 27 | case Method::G_GENETIC: return nlopt::GN_ESCH; 28 | default: assert(false); throw(m); 29 | } 30 | } 31 | 32 | /** 33 | * Optimizer based on NLopt. 34 | * 35 | * All the optimized types have to be convertible to double. 36 | */ 37 | class NloptOptimizer: public Optimizer { 38 | protected: 39 | nlopt::opt opt_; 40 | std::vector lower_bounds_; 41 | std::vector upper_bounds_; 42 | std::vector initvals_; 43 | nlopt::algorithm alg_; 44 | Method localmethod_; 45 | 46 | using Base = Optimizer; 47 | 48 | friend Base; 49 | 50 | // ********************************************************************** */ 51 | 52 | // TODO: CHANGE FOR LAMBDAS WHEN WE WILL MOVE TO C++14 53 | 54 | struct BoundsFunc { 55 | NloptOptimizer& self; 56 | inline explicit BoundsFunc(NloptOptimizer& o): self(o) {} 57 | 58 | template void operator()(int N, T& bounds) 59 | { 60 | self.lower_bounds_[N] = bounds.min(); 61 | self.upper_bounds_[N] = bounds.max(); 62 | } 63 | }; 64 | 65 | struct InitValFunc { 66 | NloptOptimizer& self; 67 | inline explicit InitValFunc(NloptOptimizer& o): self(o) {} 68 | 69 | template void operator()(int N, T& initval) 70 | { 71 | self.initvals_[N] = initval; 72 | } 73 | }; 74 | 75 | struct ResultCopyFunc { 76 | NloptOptimizer& self; 77 | inline explicit ResultCopyFunc(NloptOptimizer& o): self(o) {} 78 | 79 | template void operator()(int N, T& resultval) 80 | { 81 | resultval = self.initvals_[N]; 82 | } 83 | }; 84 | 85 | struct FunvalCopyFunc { 86 | using D = const std::vector; 87 | D& params; 88 | inline explicit FunvalCopyFunc(D& p): params(p) {} 89 | 90 | template void operator()(int N, T& resultval) 91 | { 92 | resultval = params[N]; 93 | } 94 | }; 95 | 96 | /* ********************************************************************** */ 97 | 98 | template 99 | static double optfunc(const std::vector& params, 100 | std::vector& /*grad*/, 101 | void *data) 102 | { 103 | using TData = std::pair*, NloptOptimizer*>; 104 | auto typeddata = static_cast(data); 105 | 106 | if(typeddata->second->stopcr_.stop_condition()) 107 | typeddata->second->opt_.force_stop(); 108 | 109 | auto fnptr = typeddata->first; 110 | auto funval = std::tuple(); 111 | 112 | // copy the obtained objectfunction arguments to the funval tuple. 113 | metaloop::apply(FunvalCopyFunc(params), funval); 114 | 115 | auto ret = metaloop::callFunWithTuple(*fnptr, funval, 116 | index_sequence_for()); 117 | 118 | return ret; 119 | } 120 | 121 | template 122 | Result optimize(Func&& func, 123 | std::tuple initvals, 124 | Bound... args) 125 | { 126 | lower_bounds_.resize(sizeof...(Args)); 127 | upper_bounds_.resize(sizeof...(Args)); 128 | initvals_.resize(sizeof...(Args)); 129 | 130 | opt_ = nlopt::opt(alg_, sizeof...(Args) ); 131 | 132 | // Copy the bounds which is obtained as a parameter pack in args into 133 | // lower_bounds_ and upper_bounds_ 134 | metaloop::apply(BoundsFunc(*this), args...); 135 | 136 | opt_.set_lower_bounds(lower_bounds_); 137 | opt_.set_upper_bounds(upper_bounds_); 138 | 139 | nlopt::opt localopt; 140 | switch(opt_.get_algorithm()) { 141 | case nlopt::GN_MLSL: 142 | case nlopt::GN_MLSL_LDS: 143 | localopt = nlopt::opt(method2nloptAlg(localmethod_), 144 | sizeof...(Args)); 145 | localopt.set_lower_bounds(lower_bounds_); 146 | localopt.set_upper_bounds(upper_bounds_); 147 | opt_.set_local_optimizer(localopt); 148 | default: ; 149 | } 150 | 151 | double abs_diff = stopcr_.absolute_score_difference; 152 | double rel_diff = stopcr_.relative_score_difference; 153 | double stopval = stopcr_.stop_score; 154 | if(!std::isnan(abs_diff)) opt_.set_ftol_abs(abs_diff); 155 | if(!std::isnan(rel_diff)) opt_.set_ftol_rel(rel_diff); 156 | if(!std::isnan(stopval)) opt_.set_stopval(stopval); 157 | 158 | if(this->stopcr_.max_iterations > 0) 159 | opt_.set_maxeval(this->stopcr_.max_iterations ); 160 | 161 | // Take care of the initial values, copy them to initvals_ 162 | metaloop::apply(InitValFunc(*this), initvals); 163 | 164 | std::pair*, NloptOptimizer*> data = 165 | std::make_pair(&func, this); 166 | 167 | switch(dir_) { 168 | case OptDir::MIN: 169 | opt_.set_min_objective(optfunc, &data); break; 170 | case OptDir::MAX: 171 | opt_.set_max_objective(optfunc, &data); break; 172 | } 173 | 174 | Result result; 175 | nlopt::result rescode; 176 | 177 | try { 178 | rescode = opt_.optimize(initvals_, result.score); 179 | result.resultcode = static_cast(rescode); 180 | } catch( nlopt::forced_stop& ) { 181 | result.resultcode = ResultCodes::FORCED_STOP; 182 | } 183 | 184 | metaloop::apply(ResultCopyFunc(*this), result.optimum); 185 | 186 | return result; 187 | } 188 | 189 | public: 190 | inline explicit NloptOptimizer(nlopt::algorithm alg, 191 | StopCriteria stopcr = {}): 192 | Base(stopcr), alg_(alg), localmethod_(Method::L_SIMPLEX) {} 193 | 194 | }; 195 | 196 | } 197 | } 198 | 199 | 200 | #endif // NLOPT_BOILERPLATE_HPP 201 | -------------------------------------------------------------------------------- /include/libnest2d/optimizers/nlopt/simplex.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLEX_HPP 2 | #define SIMPLEX_HPP 3 | 4 | #include "nlopt_boilerplate.hpp" 5 | 6 | namespace libnest2d { namespace opt { 7 | 8 | class SimplexOptimizer: public NloptOptimizer { 9 | public: 10 | inline explicit SimplexOptimizer(const StopCriteria& scr = {}): 11 | NloptOptimizer(method2nloptAlg(Method::L_SIMPLEX), scr) {} 12 | }; 13 | 14 | template<> 15 | struct OptimizerSubclass { using Type = SimplexOptimizer; }; 16 | 17 | } 18 | } 19 | 20 | #endif // SIMPLEX_HPP 21 | -------------------------------------------------------------------------------- /include/libnest2d/optimizers/nlopt/subplex.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SUBPLEX_HPP 2 | #define SUBPLEX_HPP 3 | 4 | #include "nlopt_boilerplate.hpp" 5 | 6 | namespace libnest2d { namespace opt { 7 | 8 | class SubplexOptimizer: public NloptOptimizer { 9 | public: 10 | inline explicit SubplexOptimizer(const StopCriteria& scr = {}): 11 | NloptOptimizer(method2nloptAlg(Method::L_SUBPLEX), scr) {} 12 | }; 13 | 14 | template<> 15 | struct OptimizerSubclass { using Type = SubplexOptimizer; }; 16 | 17 | } 18 | } 19 | 20 | #endif // SUBPLEX_HPP 21 | -------------------------------------------------------------------------------- /include/libnest2d/optimizers/optimlib/particleswarm.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PARTICLESWARM_HPP 2 | #define PARTICLESWARM_HPP 3 | 4 | #include 5 | 6 | namespace libnest2d { namespace opt { 7 | 8 | class ParticleswarmOptimizer { 9 | public: 10 | inline explicit ParticleswarmOptimizer(const StopCriteria& scr = {}) {} 11 | 12 | inline ParticleswarmOptimizer& localMethod(Method m) { 13 | localmethod_ = m; 14 | return *this; 15 | } 16 | 17 | inline void seed(unsigned long val) { nlopt::srand(val); } 18 | }; 19 | 20 | template<> 21 | struct OptimizerSubclass { using Type = ParticleswarmOptimizer; }; 22 | 23 | template<> 24 | inline TOptimizer GlobalOptimizer( 25 | Method localm, const StopCriteria& scr ) 26 | { 27 | return ParticleswarmOptimizer (scr).localMethod(localm); 28 | } 29 | 30 | } 31 | } 32 | 33 | #endif // GENETIC_HPP 34 | -------------------------------------------------------------------------------- /include/libnest2d/parallel.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIBNEST2D_PARALLEL_HPP 2 | #define LIBNEST2D_PARALLEL_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef LIBNEST2D_THREADING_tbb 9 | #include 10 | #endif 11 | 12 | #ifdef LIBNEST2D_THREADING_omp 13 | #include 14 | #endif 15 | 16 | namespace libnest2d { namespace __parallel { 17 | 18 | template 19 | using TIteratorValue = typename std::iterator_traits::value_type; 20 | 21 | template 22 | inline void enumerate( 23 | Iterator from, Iterator to, 24 | std::function, size_t)> fn, 25 | std::launch policy = std::launch::deferred | std::launch::async) 26 | { 27 | using TN = size_t; 28 | auto iN = to-from; 29 | TN N = iN < 0? 0 : TN(iN); 30 | 31 | #ifdef LIBNEST2D_THREADING_tbb 32 | if((policy & std::launch::async) == std::launch::async) { 33 | tbb::parallel_for(0, N, [from, fn] (TN n) { fn(*(from + n), n); } ); 34 | } else { 35 | for(TN n = 0; n < N; n++) fn(*(from + n), n); 36 | } 37 | #endif 38 | 39 | #ifdef LIBNEST2D_THREADING_omp 40 | if((policy & std::launch::async) == std::launch::async) { 41 | #pragma omp parallel for 42 | for(int n = 0; n < int(N); n++) fn(*(from + n), TN(n)); 43 | } 44 | else { 45 | for(TN n = 0; n < N; n++) fn(*(from + n), n); 46 | } 47 | #endif 48 | 49 | #ifdef LIBNEST2D_THREADING_std 50 | std::vector> rets(N); 51 | 52 | auto it = from; 53 | for(TN b = 0; b < N; b++) { 54 | rets[b] = std::async(policy, fn, *it++, unsigned(b)); 55 | } 56 | 57 | for(TN fi = 0; fi < N; ++fi) rets[fi].wait(); 58 | #endif 59 | } 60 | 61 | }} 62 | 63 | #endif //LIBNEST2D_PARALLEL_HPP 64 | -------------------------------------------------------------------------------- /include/libnest2d/placers/bottomleftplacer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BOTTOMLEFT_HPP 2 | #define BOTTOMLEFT_HPP 3 | 4 | #include 5 | 6 | #include "placer_boilerplate.hpp" 7 | 8 | namespace libnest2d { namespace placers { 9 | 10 | template struct DefaultEpsilon {}; 11 | 12 | template 13 | struct DefaultEpsilon::value, T> > { 14 | static const T Value = 1; 15 | }; 16 | 17 | template 18 | struct DefaultEpsilon::value, T> > { 19 | static const T Value = 1e-3; 20 | }; 21 | 22 | template 23 | struct BLConfig { 24 | DECLARE_MAIN_TYPES(RawShape); 25 | 26 | Coord min_obj_distance = 0; 27 | Coord epsilon = DefaultEpsilon::Value; 28 | bool allow_rotations = false; 29 | }; 30 | 31 | template 32 | class _BottomLeftPlacer: public PlacerBoilerplate< 33 | _BottomLeftPlacer, 34 | RawShape, _Box>, 35 | BLConfig > 36 | { 37 | using Base = PlacerBoilerplate<_BottomLeftPlacer, RawShape, 38 | _Box>, BLConfig>; 39 | DECLARE_PLACER(Base) 40 | 41 | public: 42 | 43 | explicit _BottomLeftPlacer(const BinType& bin): Base(bin) {} 44 | 45 | template> 46 | PackResult trypack(Item& item, 47 | const Range& = Range()) 48 | { 49 | auto r = _trypack(item); 50 | if(!r && Base::config_.allow_rotations) { 51 | 52 | item.rotate(Degrees(90)); 53 | r =_trypack(item); 54 | } 55 | return r; 56 | } 57 | 58 | enum class Dir { 59 | LEFT, 60 | DOWN 61 | }; 62 | 63 | inline RawShape leftPoly(const Item& item) const { 64 | return toWallPoly(item, Dir::LEFT); 65 | } 66 | 67 | inline RawShape downPoly(const Item& item) const { 68 | return toWallPoly(item, Dir::DOWN); 69 | } 70 | 71 | inline Coord availableSpaceLeft(const Item& item) { 72 | return availableSpace(item, Dir::LEFT); 73 | } 74 | 75 | inline Coord availableSpaceDown(const Item& item) { 76 | return availableSpace(item, Dir::DOWN); 77 | } 78 | 79 | protected: 80 | 81 | PackResult _trypack(Item& item) { 82 | 83 | // Get initial position for item in the top right corner 84 | setInitialPosition(item); 85 | 86 | Coord d = availableSpaceDown(item); 87 | auto eps = config_.epsilon; 88 | bool can_move = d > eps; 89 | bool can_be_packed = can_move; 90 | bool left = true; 91 | 92 | while(can_move) { 93 | if(left) { // write previous down move and go down 94 | item.translate({0, -d+eps}); 95 | d = availableSpaceLeft(item); 96 | can_move = d > eps; 97 | left = false; 98 | } else { // write previous left move and go down 99 | item.translate({-d+eps, 0}); 100 | d = availableSpaceDown(item); 101 | can_move = d > eps; 102 | left = true; 103 | } 104 | } 105 | 106 | if(can_be_packed) { 107 | Item trsh(item.transformedShape()); 108 | for(auto& v : trsh) can_be_packed = can_be_packed && 109 | getX(v) < bin_.width() && 110 | getY(v) < bin_.height(); 111 | } 112 | 113 | return can_be_packed? PackResult(item) : PackResult(); 114 | } 115 | 116 | void setInitialPosition(Item& item) { 117 | auto bb = item.boundingBox(); 118 | 119 | Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) }; 120 | 121 | 122 | Coord dx = getX(bin_.maxCorner()) - getX(v); 123 | Coord dy = getY(bin_.maxCorner()) - getY(v); 124 | 125 | item.translate({dx, dy}); 126 | } 127 | 128 | template 129 | static enable_if_t::value, bool> 130 | isInTheWayOf( const Item& item, 131 | const Item& other, 132 | const RawShape& scanpoly) 133 | { 134 | auto tsh = other.transformedShape(); 135 | return ( sl::intersects(tsh, scanpoly) || 136 | sl::isInside(tsh, scanpoly) ) && 137 | ( !sl::intersects(tsh, item.rawShape()) && 138 | !sl::isInside(tsh, item.rawShape()) ); 139 | } 140 | 141 | template 142 | static enable_if_t::value, bool> 143 | isInTheWayOf( const Item& item, 144 | const Item& other, 145 | const RawShape& scanpoly) 146 | { 147 | auto tsh = other.transformedShape(); 148 | 149 | bool inters_scanpoly = sl::intersects(tsh, scanpoly) && 150 | !sl::touches(tsh, scanpoly); 151 | bool inters_item = sl::intersects(tsh, item.rawShape()) && 152 | !sl::touches(tsh, item.rawShape()); 153 | 154 | return ( inters_scanpoly || 155 | sl::isInside(tsh, scanpoly)) && 156 | ( !inters_item && 157 | !sl::isInside(tsh, item.rawShape()) 158 | ); 159 | } 160 | 161 | ItemGroup itemsInTheWayOf(const Item& item, const Dir dir) { 162 | // Get the left or down polygon, that has the same area as the shadow 163 | // of input item reflected to the left or downwards 164 | auto&& scanpoly = dir == Dir::LEFT? leftPoly(item) : 165 | downPoly(item); 166 | 167 | ItemGroup ret; // packed items 'in the way' of item 168 | ret.reserve(items_.size()); 169 | 170 | // Predicate to find items that are 'in the way' for left (down) move 171 | auto predicate = [&scanpoly, &item](const Item& it) { 172 | return isInTheWayOf(item, it, scanpoly); 173 | }; 174 | 175 | // Get the items that are in the way for the left (or down) movement 176 | std::copy_if(items_.begin(), items_.end(), 177 | std::back_inserter(ret), predicate); 178 | 179 | return ret; 180 | } 181 | 182 | Coord availableSpace(const Item& _item, const Dir dir) { 183 | 184 | Item item (_item.transformedShape()); 185 | 186 | 187 | std::function getCoord; 188 | std::function< std::pair(const Segment&, const Vertex&) > 189 | availableDistanceSV; 190 | 191 | std::function< std::pair(const Vertex&, const Segment&) > 192 | availableDistance; 193 | 194 | if(dir == Dir::LEFT) { 195 | getCoord = [](const Vertex& v) { return getX(v); }; 196 | availableDistance = pointlike::horizontalDistance; 197 | availableDistanceSV = [](const Segment& s, const Vertex& v) { 198 | auto ret = pointlike::horizontalDistance(v, s); 199 | if(ret.second) ret.first = -ret.first; 200 | return ret; 201 | }; 202 | } 203 | else { 204 | getCoord = [](const Vertex& v) { return getY(v); }; 205 | availableDistance = pointlike::verticalDistance; 206 | availableDistanceSV = [](const Segment& s, const Vertex& v) { 207 | auto ret = pointlike::verticalDistance(v, s); 208 | if(ret.second) ret.first = -ret.first; 209 | return ret; 210 | }; 211 | } 212 | 213 | auto&& items_in_the_way = itemsInTheWayOf(item, dir); 214 | 215 | // Comparison function for finding min vertex 216 | auto cmp = [&getCoord](const Vertex& v1, const Vertex& v2) { 217 | return getCoord(v1) < getCoord(v2); 218 | }; 219 | 220 | // find minimum left or down coordinate of item 221 | auto minvertex_it = std::min_element(item.begin(), 222 | item.end(), 223 | cmp); 224 | 225 | // Get the initial distance in floating point 226 | Coord m = getCoord(*minvertex_it); 227 | 228 | // Check available distance for every vertex of item to the objects 229 | // in the way for the nearest intersection 230 | if(!items_in_the_way.empty()) { // This is crazy, should be optimized... 231 | for(Item& pleft : items_in_the_way) { 232 | // For all segments in items_to_left 233 | 234 | assert(pleft.vertexCount() > 0); 235 | 236 | auto trpleft_poly = pleft.transformedShape(); 237 | auto& trpleft = sl::contour(trpleft_poly); 238 | auto first = sl::begin(trpleft); 239 | auto next = first + 1; 240 | auto endit = sl::end(trpleft); 241 | 242 | while(next != endit) { 243 | Segment seg(*(first++), *(next++)); 244 | for(auto& v : item) { // For all vertices in item 245 | 246 | auto d = availableDistance(v, seg); 247 | 248 | if(d.second && d.first < m) m = d.first; 249 | } 250 | } 251 | } 252 | 253 | auto first = item.begin(); 254 | auto next = first + 1; 255 | auto endit = item.end(); 256 | 257 | // For all edges in item: 258 | while(next != endit) { 259 | Segment seg(*(first++), *(next++)); 260 | 261 | // for all shapes in items_to_left 262 | for(Item& sh : items_in_the_way) { 263 | assert(sh.vertexCount() > 0); 264 | 265 | Item tsh(sh.transformedShape()); 266 | for(auto& v : tsh) { // For all vertices in item 267 | 268 | auto d = availableDistanceSV(seg, v); 269 | 270 | if(d.second && d.first < m) m = d.first; 271 | } 272 | } 273 | } 274 | } 275 | 276 | return m; 277 | } 278 | 279 | /** 280 | * Implementation of the left (and down) polygon as described by 281 | * [López-Camacho et al. 2013]\ 282 | * (http://www.cs.stir.ac.uk/~goc/papers/EffectiveHueristic2DAOR2013.pdf) 283 | * see algorithm 8 for details... 284 | */ 285 | RawShape toWallPoly(const Item& _item, const Dir dir) const { 286 | // The variable names reflect the case of left polygon calculation. 287 | // 288 | // We will iterate through the item's vertices and search for the top 289 | // and bottom vertices (or right and left if dir==Dir::DOWN). 290 | // Save the relevant vertices and their indices into `bottom` and 291 | // `top` vectors. In case of left polygon construction these will 292 | // contain the top and bottom polygons which have the same vertical 293 | // coordinates (in case there is more of them). 294 | // 295 | // We get the leftmost (or downmost) vertex from the `bottom` and `top` 296 | // vectors and construct the final polygon. 297 | 298 | Item item (_item.transformedShape()); 299 | 300 | auto getCoord = [dir](const Vertex& v) { 301 | return dir == Dir::LEFT? getY(v) : getX(v); 302 | }; 303 | 304 | Coord max_y = std::numeric_limits::min(); 305 | Coord min_y = std::numeric_limits::max(); 306 | 307 | using El = std::pair>; 308 | 309 | std::function cmp; 310 | 311 | if(dir == Dir::LEFT) 312 | cmp = [](const El& e1, const El& e2) { 313 | return getX(e1.second.get()) < getX(e2.second.get()); 314 | }; 315 | else 316 | cmp = [](const El& e1, const El& e2) { 317 | return getY(e1.second.get()) < getY(e2.second.get()); 318 | }; 319 | 320 | std::vector< El > top; 321 | std::vector< El > bottom; 322 | 323 | size_t idx = 0; 324 | for(auto& v : item) { // Find the bottom and top vertices and save them 325 | auto vref = std::cref(v); 326 | auto vy = getCoord(v); 327 | 328 | if( vy > max_y ) { 329 | max_y = vy; 330 | top.clear(); 331 | top.emplace_back(idx, vref); 332 | } 333 | else if(vy == max_y) { top.emplace_back(idx, vref); } 334 | 335 | if(vy < min_y) { 336 | min_y = vy; 337 | bottom.clear(); 338 | bottom.emplace_back(idx, vref); 339 | } 340 | else if(vy == min_y) { bottom.emplace_back(idx, vref); } 341 | 342 | idx++; 343 | } 344 | 345 | // Get the top and bottom leftmost vertices, or the right and left 346 | // downmost vertices (if dir == Dir::DOWN) 347 | auto topleft_it = std::min_element(top.begin(), top.end(), cmp); 348 | auto bottomleft_it = 349 | std::min_element(bottom.begin(), bottom.end(), cmp); 350 | 351 | auto& topleft_vertex = topleft_it->second.get(); 352 | auto& bottomleft_vertex = bottomleft_it->second.get(); 353 | 354 | // Start and finish positions for the vertices that will be part of the 355 | // new polygon 356 | auto start = std::min(topleft_it->first, bottomleft_it->first); 357 | auto finish = std::max(topleft_it->first, bottomleft_it->first); 358 | 359 | RawShape ret; 360 | 361 | // the return shape 362 | auto& rsh = sl::contour(ret); 363 | 364 | // reserve for all vertices plus 2 for the left horizontal wall, 2 for 365 | // the additional vertices for maintaning min object distance 366 | sl::reserve(rsh, finish-start+4); 367 | 368 | /*auto addOthers = [&rsh, finish, start, &item](){ 369 | for(size_t i = start+1; i < finish; i++) 370 | sl::addVertex(rsh, item.vertex(i)); 371 | };*/ 372 | 373 | auto reverseAddOthers = [&rsh, finish, start, &item](){ 374 | for(auto i = finish-1; i > start; i--) 375 | sl::addVertex(rsh, item.vertex( 376 | static_cast(i))); 377 | }; 378 | 379 | // Final polygon construction... 380 | 381 | static_assert(OrientationType::Value == 382 | Orientation::CLOCKWISE, 383 | "Counter clockwise toWallPoly() Unimplemented!"); 384 | 385 | // Clockwise polygon construction 386 | 387 | sl::addVertex(rsh, topleft_vertex); 388 | 389 | if(dir == Dir::LEFT) reverseAddOthers(); 390 | else { 391 | sl::addVertex(rsh, getX(topleft_vertex), 0); 392 | sl::addVertex(rsh, getX(bottomleft_vertex), 0); 393 | } 394 | 395 | sl::addVertex(rsh, bottomleft_vertex); 396 | 397 | if(dir == Dir::LEFT) { 398 | sl::addVertex(rsh, 0, getY(bottomleft_vertex)); 399 | sl::addVertex(rsh, 0, getY(topleft_vertex)); 400 | } 401 | else reverseAddOthers(); 402 | 403 | 404 | // Close the polygon 405 | sl::addVertex(rsh, topleft_vertex); 406 | 407 | return ret; 408 | } 409 | 410 | }; 411 | 412 | } 413 | } 414 | 415 | #endif //BOTTOMLEFT_HPP 416 | -------------------------------------------------------------------------------- /include/libnest2d/placers/placer_boilerplate.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PLACER_BOILERPLATE_HPP 2 | #define PLACER_BOILERPLATE_HPP 3 | 4 | #include 5 | 6 | namespace libnest2d { namespace placers { 7 | 8 | struct EmptyConfig {}; 9 | 10 | template 11 | class PlacerBoilerplate { 12 | mutable bool farea_valid_ = false; 13 | mutable double farea_ = 0.0; 14 | public: 15 | using ShapeType = RawShape; 16 | using Item = _Item; 17 | using Vertex = TPoint; 18 | using Segment = _Segment; 19 | using BinType = TBin; 20 | using Coord = TCoord; 21 | using Config = Cfg; 22 | using ItemGroup = _ItemGroup; 23 | using DefaultIter = typename ItemGroup::const_iterator; 24 | 25 | class PackResult { 26 | Item *item_ptr_; 27 | Vertex move_; 28 | Radians rot_; 29 | double overfit_; 30 | friend class PlacerBoilerplate; 31 | friend Subclass; 32 | 33 | PackResult(Item& item): 34 | item_ptr_(&item), 35 | move_(item.translation()), 36 | rot_(item.rotation()) {} 37 | 38 | PackResult(double overfit = 1.0): 39 | item_ptr_(nullptr), overfit_(overfit) {} 40 | 41 | public: 42 | operator bool() { return item_ptr_ != nullptr; } 43 | double overfit() const { return overfit_; } 44 | }; 45 | 46 | inline PlacerBoilerplate(const BinType& bin, unsigned cap = 50): bin_(bin) 47 | { 48 | items_.reserve(cap); 49 | } 50 | 51 | inline const BinType& bin() const BP2D_NOEXCEPT { return bin_; } 52 | 53 | template inline void bin(TB&& b) { 54 | bin_ = std::forward(b); 55 | } 56 | 57 | inline void configure(const Config& config) BP2D_NOEXCEPT { 58 | config_ = config; 59 | } 60 | 61 | template> 62 | bool pack(Item& item, const Range& rem = Range()) { 63 | auto&& r = static_cast(this)->trypack(item, rem); 64 | if(r) { 65 | items_.emplace_back(*(r.item_ptr_)); 66 | farea_valid_ = false; 67 | } 68 | return r; 69 | } 70 | 71 | void preload(const ItemGroup& packeditems) { 72 | items_.insert(items_.end(), packeditems.begin(), packeditems.end()); 73 | farea_valid_ = false; 74 | } 75 | 76 | void accept(PackResult& r) { 77 | if(r) { 78 | r.item_ptr_->translation(r.move_); 79 | r.item_ptr_->rotation(r.rot_); 80 | items_.emplace_back(*(r.item_ptr_)); 81 | farea_valid_ = false; 82 | } 83 | } 84 | 85 | void unpackLast() { 86 | items_.pop_back(); 87 | farea_valid_ = false; 88 | } 89 | 90 | inline const ItemGroup& getItems() const { return items_; } 91 | 92 | inline void clearItems() { 93 | items_.clear(); 94 | farea_valid_ = false; 95 | } 96 | 97 | inline double filledArea() const { 98 | if(farea_valid_) return farea_; 99 | else { 100 | farea_ = .0; 101 | std::for_each(items_.begin(), items_.end(), 102 | [this] (Item& item) { 103 | farea_ += item.area(); 104 | }); 105 | farea_valid_ = true; 106 | } 107 | 108 | return farea_; 109 | } 110 | 111 | protected: 112 | 113 | BinType bin_; 114 | ItemGroup items_; 115 | Cfg config_; 116 | }; 117 | 118 | 119 | #define DECLARE_PLACER(Base) \ 120 | using Base::bin_; \ 121 | using Base::items_; \ 122 | using Base::config_; \ 123 | public: \ 124 | using typename Base::ShapeType; \ 125 | using typename Base::Item; \ 126 | using typename Base::ItemGroup; \ 127 | using typename Base::BinType; \ 128 | using typename Base::Config; \ 129 | using typename Base::Vertex; \ 130 | using typename Base::Segment; \ 131 | using typename Base::PackResult; \ 132 | using typename Base::Coord; \ 133 | private: 134 | 135 | } 136 | } 137 | 138 | #endif // PLACER_BOILERPLATE_HPP 139 | -------------------------------------------------------------------------------- /include/libnest2d/selections/filler.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FILLER_HPP 2 | #define FILLER_HPP 3 | 4 | #include "selection_boilerplate.hpp" 5 | 6 | namespace libnest2d { namespace selections { 7 | 8 | template 9 | class _FillerSelection: public SelectionBoilerplate { 10 | using Base = SelectionBoilerplate; 11 | public: 12 | using typename Base::Item; 13 | using Config = int; //dummy 14 | 15 | private: 16 | using Base::packed_bins_; 17 | using typename Base::ItemGroup; 18 | using Container = ItemGroup; 19 | Container store_; 20 | 21 | public: 22 | 23 | void configure(const Config& /*config*/) { } 24 | 25 | template::BinType, 27 | class PConfig = typename PlacementStrategyLike::Config> 28 | void packItems(TIterator first, 29 | TIterator last, 30 | TBin&& bin, 31 | PConfig&& pconfig = PConfig()) 32 | { 33 | using Placer = PlacementStrategyLike; 34 | 35 | store_.clear(); 36 | auto total = last-first; 37 | store_.reserve(total); 38 | 39 | // TODO: support preloading 40 | packed_bins_.clear(); 41 | 42 | packed_bins_.emplace_back(); 43 | 44 | auto makeProgress = [this, &total]( 45 | PlacementStrategyLike& placer) 46 | { 47 | packed_bins_.back() = placer.getItems(); 48 | #ifndef NDEBUG 49 | packed_bins_.back().insert(packed_bins_.back().end(), 50 | placer.getDebugItems().begin(), 51 | placer.getDebugItems().end()); 52 | #endif 53 | this->progress_(--total); 54 | }; 55 | 56 | std::copy(first, last, std::back_inserter(store_)); 57 | 58 | auto sortfunc = [](Item& i1, Item& i2) { 59 | return i1.area() > i2.area(); 60 | }; 61 | 62 | this->template remove_unpackable_items(store_, bin, pconfig); 63 | 64 | std::sort(store_.begin(), store_.end(), sortfunc); 65 | 66 | Placer placer(bin); 67 | placer.configure(pconfig); 68 | 69 | auto it = store_.begin(); 70 | while(it != store_.end() && !this->stopcond_()) { 71 | if(!placer.pack(*it, {std::next(it), store_.end()})) { 72 | if(packed_bins_.back().empty()) ++it; 73 | placer.clearItems(); 74 | packed_bins_.emplace_back(); 75 | } else { 76 | makeProgress(placer); 77 | ++it; 78 | } 79 | } 80 | 81 | } 82 | }; 83 | 84 | } 85 | } 86 | 87 | #endif //BOTTOMLEFT_HPP 88 | -------------------------------------------------------------------------------- /include/libnest2d/selections/firstfit.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FIRSTFIT_HPP 2 | #define FIRSTFIT_HPP 3 | 4 | #include "selection_boilerplate.hpp" 5 | 6 | namespace libnest2d { namespace selections { 7 | 8 | template 9 | class _FirstFitSelection: public SelectionBoilerplate { 10 | using Base = SelectionBoilerplate; 11 | public: 12 | using typename Base::Item; 13 | using Config = int; //dummy 14 | 15 | private: 16 | using Base::packed_bins_; 17 | using typename Base::ItemGroup; 18 | using Container = ItemGroup;//typename std::vector<_Item>; 19 | 20 | Container store_; 21 | 22 | public: 23 | 24 | void configure(const Config& /*config*/) { } 25 | 26 | template::BinType, 28 | class PConfig = typename PlacementStrategyLike::Config> 29 | void packItems(TIterator first, 30 | TIterator last, 31 | TBin&& bin, 32 | PConfig&& pconfig = PConfig()) 33 | { 34 | 35 | using Placer = PlacementStrategyLike; 36 | 37 | store_.clear(); 38 | store_.reserve(last-first); 39 | 40 | std::vector placers; 41 | placers.reserve(last-first); 42 | 43 | std::for_each(first, last, [this](Item& itm) { 44 | if(itm.isFixed()) { 45 | if (itm.binId() < 0) itm.binId(0); 46 | auto binidx = size_t(itm.binId()); 47 | 48 | while(packed_bins_.size() <= binidx) 49 | packed_bins_.emplace_back(); 50 | 51 | packed_bins_[binidx].emplace_back(itm); 52 | } else { 53 | store_.emplace_back(itm); 54 | } 55 | }); 56 | 57 | // If the packed_items array is not empty we have to create as many 58 | // placers as there are elements in packed bins and preload each item 59 | // into the appropriate placer 60 | for(ItemGroup& ig : packed_bins_) { 61 | placers.emplace_back(bin); 62 | placers.back().configure(pconfig); 63 | placers.back().preload(ig); 64 | } 65 | 66 | auto sortfunc = [](Item& i1, Item& i2) { 67 | int p1 = i1.priority(), p2 = i2.priority(); 68 | return p1 == p2 ? i1.area() > i2.area() : p1 > p2; 69 | }; 70 | 71 | std::sort(store_.begin(), store_.end(), sortfunc); 72 | 73 | auto total = last-first; 74 | auto makeProgress = [this, &total](Placer& placer, size_t idx) { 75 | packed_bins_[idx] = placer.getItems(); 76 | this->progress_(static_cast(--total)); 77 | }; 78 | 79 | auto& cancelled = this->stopcond_; 80 | 81 | this->template remove_unpackable_items(store_, bin, pconfig); 82 | 83 | auto it = store_.begin(); 84 | 85 | while(it != store_.end() && !cancelled()) { 86 | bool was_packed = false; 87 | size_t j = 0; 88 | while(!was_packed && !cancelled()) { 89 | for(; j < placers.size() && !was_packed && !cancelled(); j++) { 90 | if((was_packed = placers[j].pack(*it, rem(it, store_) ))) { 91 | it->get().binId(int(j)); 92 | makeProgress(placers[j], j); 93 | } 94 | } 95 | 96 | if(!was_packed) { 97 | placers.emplace_back(bin); 98 | placers.back().configure(pconfig); 99 | packed_bins_.emplace_back(); 100 | j = placers.size() - 1; 101 | } 102 | } 103 | ++it; 104 | } 105 | } 106 | 107 | }; 108 | 109 | } 110 | } 111 | 112 | #endif // FIRSTFIT_HPP 113 | -------------------------------------------------------------------------------- /include/libnest2d/selections/selection_boilerplate.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SELECTION_BOILERPLATE_HPP 2 | #define SELECTION_BOILERPLATE_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace libnest2d { namespace selections { 8 | 9 | template 10 | class SelectionBoilerplate { 11 | public: 12 | using ShapeType = RawShape; 13 | using Item = _Item; 14 | using ItemGroup = _ItemGroup; 15 | using PackGroup = _PackGroup; 16 | 17 | inline const PackGroup& getResult() const { 18 | return packed_bins_; 19 | } 20 | 21 | inline void progressIndicator(ProgressFunction fn) { progress_ = fn; } 22 | 23 | inline void stopCondition(StopCondition cond) { stopcond_ = cond; } 24 | 25 | inline void clear() { packed_bins_.clear(); } 26 | 27 | protected: 28 | 29 | template 30 | void remove_unpackable_items(Container &c, const Bin &bin, const PCfg& pcfg) 31 | { 32 | // Safety test: try to pack each item into an empty bin. If it fails 33 | // then it should be removed from the list 34 | auto it = c.begin(); 35 | while (it != c.end() && !stopcond_()) { 36 | 37 | // WARNING: The copy of itm needs to be created before Placer. 38 | // Placer is working with references and its destructor still 39 | // manipulates the item this is why the order of stack creation 40 | // matters here. 41 | const Item& itm = *it; 42 | Item cpy{itm}; 43 | 44 | Placer p{bin}; 45 | p.configure(pcfg); 46 | if (itm.area() <= 0 || !p.pack(cpy)) it = c.erase(it); 47 | else it++; 48 | } 49 | } 50 | 51 | PackGroup packed_bins_; 52 | ProgressFunction progress_ = [](unsigned){}; 53 | StopCondition stopcond_ = [](){ return false; }; 54 | }; 55 | 56 | } 57 | } 58 | 59 | #endif // SELECTION_BOILERPLATE_HPP 60 | -------------------------------------------------------------------------------- /include/libnest2d/utils/bigint.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIBNEST2D_BIGINT_HPP 2 | #define LIBNEST2D_BIGINT_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace libnest2d { 11 | 12 | template using Bits = std::integral_constant; 13 | 14 | template class BigInt { 15 | static const int N = Bits::value / (8 * sizeof(I)) + 16 | Bits::value % (8 * sizeof(I)) / 8; 17 | 18 | static const int IBase = sizeof(I) * 8; 19 | 20 | std::array v_; 21 | int sign_ = 1; 22 | public: 23 | 24 | BigInt() = default; 25 | 26 | explicit constexpr inline BigInt(I v) : 27 | v_({v < 0 ? -v : v}), sign_(v < 0 ? -1 : 1) 28 | { 29 | static_assert(std::is_integral::value, 30 | "Only integral types are allowed for BigInt!"); 31 | } 32 | 33 | BigInt& operator+=(const BigInt& /*o*/) { 34 | return *this; 35 | } 36 | BigInt& operator*=(const BigInt& /*o*/) { return *this; } 37 | BigInt& operator-=(const BigInt& /*o*/) { return *this; } 38 | BigInt& operator/=(const BigInt& /*o*/) { return *this; } 39 | 40 | BigInt& operator+=(I /*o*/) { return *this; } 41 | BigInt& operator*=(I /*o*/) { return *this; } 42 | BigInt& operator-=(I /*o*/) { return *this; } 43 | BigInt& operator/=(I /*o*/) { return *this; } 44 | 45 | BigInt operator+(const BigInt& /*o*/) const { return *this; } 46 | BigInt operator*(const BigInt& /*o*/) const { return *this; } 47 | BigInt operator-(const BigInt& /*o*/) const { return *this; } 48 | BigInt operator/(const BigInt& /*o*/) const { return *this; } 49 | 50 | BigInt operator+(I /*o*/) const { return *this; } 51 | BigInt operator*(I /*o*/) const { return *this; } 52 | BigInt operator-(I /*o*/) const { return *this; } 53 | BigInt operator/(I /*o*/) const { return *this; } 54 | 55 | BigInt operator-() const { 56 | auto cpy = *this; sign_ > 0 ? cpy.sign_ = -1 : 1; return cpy; 57 | } 58 | 59 | bool operator< (I) const { return false; } 60 | bool operator> (I) const { return false; } 61 | bool operator<=(I) const { return false; } 62 | bool operator>=(I) const { return false; } 63 | bool operator==(I) const { return false; } 64 | bool operator!=(I) const { return false; } 65 | 66 | bool operator< (const BigInt& ) const { return false; } 67 | bool operator> (const BigInt& ) const { return false; } 68 | bool operator<=(const BigInt& ) const { return false; } 69 | bool operator>=(const BigInt& ) const { return false; } 70 | bool operator==(const BigInt& ) const { return false; } 71 | bool operator!=(const BigInt& ) const { return false; } 72 | 73 | long double to_floating() const { 74 | long double r = 0.0l; int n = 0; 75 | for(I a : v_) r += static_cast(a) * std::pow(2, IBase * n); 76 | return r; 77 | } 78 | 79 | }; 80 | 81 | template using BigInt128 = BigInt>; 82 | template using BigInt256 = BigInt>; 83 | template using BigInt512 = BigInt>; 84 | 85 | template 86 | struct _NumTag> { using Type = BigIntTag; }; 87 | 88 | template T cast(const Bi& r, BigIntTag, ScalarTag) 89 | { 90 | static_assert(std::is_floating_point::value, 91 | "BigInt should only be casted to floating point type"); 92 | return static_cast(r.to_floating()); 93 | } 94 | 95 | template inline Bi abs(const Bi& v, BigIntTag) 96 | { 97 | return v < Bi(0) ? -v : v; 98 | } 99 | 100 | } 101 | 102 | #endif // BIGINT_HPP 103 | -------------------------------------------------------------------------------- /include/libnest2d/utils/boost_alg.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BOOST_ALG_HPP 2 | #define BOOST_ALG_HPP 3 | 4 | #ifndef DISABLE_BOOST_SERIALIZE 5 | #include 6 | #endif 7 | 8 | #ifdef __clang__ 9 | #undef _MSC_EXTENSIONS 10 | #endif 11 | 12 | #ifdef _MSC_VER 13 | #pragma warning(push) 14 | #pragma warning(disable: 4244) 15 | #pragma warning(disable: 4267) 16 | #endif 17 | #include 18 | #ifdef _MSC_VER 19 | #pragma warning(pop) 20 | #endif 21 | // this should be removed to not confuse the compiler 22 | // #include 23 | 24 | namespace bp2d { 25 | 26 | using libnest2d::TCoord; 27 | using libnest2d::PointImpl; 28 | using Coord = TCoord; 29 | using libnest2d::PolygonImpl; 30 | using libnest2d::PathImpl; 31 | using libnest2d::Orientation; 32 | using libnest2d::OrientationType; 33 | using libnest2d::getX; 34 | using libnest2d::getY; 35 | using libnest2d::setX; 36 | using libnest2d::setY; 37 | using Box = libnest2d::_Box; 38 | using Segment = libnest2d::_Segment; 39 | using Shapes = libnest2d::nfp::Shapes; 40 | 41 | } 42 | 43 | /** 44 | * We have to make all the libnest2d geometry types available to boost. The real 45 | * models of the geometries remain the same if a conforming model for libnest2d 46 | * was defined by the library client. Boost is used only as an optional 47 | * implementer of some algorithms that can be implemented by the model itself 48 | * if a faster alternative exists. 49 | * 50 | * However, boost has its own type traits and we have to define the needed 51 | * specializations to be able to use boost::geometry. This can be done with the 52 | * already provided model. 53 | */ 54 | namespace boost { 55 | namespace geometry { 56 | namespace traits { 57 | 58 | /* ************************************************************************** */ 59 | /* Point concept adaptaion ************************************************** */ 60 | /* ************************************************************************** */ 61 | 62 | template<> struct tag { 63 | using type = point_tag; 64 | }; 65 | 66 | template<> struct coordinate_type { 67 | using type = bp2d::Coord; 68 | }; 69 | 70 | template<> struct coordinate_system { 71 | using type = cs::cartesian; 72 | }; 73 | 74 | template<> struct dimension: boost::mpl::int_<2> {}; 75 | 76 | template<> 77 | struct access { 78 | static inline bp2d::Coord get(bp2d::PointImpl const& a) { 79 | return libnest2d::getX(a); 80 | } 81 | 82 | static inline void set(bp2d::PointImpl& a, 83 | bp2d::Coord const& value) { 84 | libnest2d::setX(a, value); 85 | } 86 | }; 87 | 88 | template<> 89 | struct access { 90 | static inline bp2d::Coord get(bp2d::PointImpl const& a) { 91 | return libnest2d::getY(a); 92 | } 93 | 94 | static inline void set(bp2d::PointImpl& a, 95 | bp2d::Coord const& value) { 96 | libnest2d::setY(a, value); 97 | } 98 | }; 99 | 100 | 101 | /* ************************************************************************** */ 102 | /* Box concept adaptaion **************************************************** */ 103 | /* ************************************************************************** */ 104 | 105 | template<> struct tag { 106 | using type = box_tag; 107 | }; 108 | 109 | template<> struct point_type { 110 | using type = bp2d::PointImpl; 111 | }; 112 | 113 | template<> struct indexed_access { 114 | static inline bp2d::Coord get(bp2d::Box const& box) { 115 | return bp2d::getX(box.minCorner()); 116 | } 117 | static inline void set(bp2d::Box &box, bp2d::Coord const& coord) { 118 | bp2d::setX(box.minCorner(), coord); 119 | } 120 | }; 121 | 122 | template<> struct indexed_access { 123 | static inline bp2d::Coord get(bp2d::Box const& box) { 124 | return bp2d::getY(box.minCorner()); 125 | } 126 | static inline void set(bp2d::Box &box, bp2d::Coord const& coord) { 127 | bp2d::setY(box.minCorner(), coord); 128 | } 129 | }; 130 | 131 | template<> struct indexed_access { 132 | static inline bp2d::Coord get(bp2d::Box const& box) { 133 | return bp2d::getX(box.maxCorner()); 134 | } 135 | static inline void set(bp2d::Box &box, bp2d::Coord const& coord) { 136 | bp2d::setX(box.maxCorner(), coord); 137 | } 138 | }; 139 | 140 | template<> struct indexed_access { 141 | static inline bp2d::Coord get(bp2d::Box const& box) { 142 | return bp2d::getY(box.maxCorner()); 143 | } 144 | static inline void set(bp2d::Box &box, bp2d::Coord const& coord) { 145 | bp2d::setY(box.maxCorner(), coord); 146 | } 147 | }; 148 | 149 | /* ************************************************************************** */ 150 | /* Segment concept adaptaion ************************************************ */ 151 | /* ************************************************************************** */ 152 | 153 | template<> struct tag { 154 | using type = segment_tag; 155 | }; 156 | 157 | template<> struct point_type { 158 | using type = bp2d::PointImpl; 159 | }; 160 | 161 | template<> struct indexed_access { 162 | static inline bp2d::Coord get(bp2d::Segment const& seg) { 163 | return bp2d::getX(seg.first()); 164 | } 165 | static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) { 166 | auto p = seg.first(); bp2d::setX(p, coord); seg.first(p); 167 | } 168 | }; 169 | 170 | template<> struct indexed_access { 171 | static inline bp2d::Coord get(bp2d::Segment const& seg) { 172 | return bp2d::getY(seg.first()); 173 | } 174 | static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) { 175 | auto p = seg.first(); bp2d::setY(p, coord); seg.first(p); 176 | } 177 | }; 178 | 179 | template<> struct indexed_access { 180 | static inline bp2d::Coord get(bp2d::Segment const& seg) { 181 | return bp2d::getX(seg.second()); 182 | } 183 | static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) { 184 | auto p = seg.second(); bp2d::setX(p, coord); seg.second(p); 185 | } 186 | }; 187 | 188 | template<> struct indexed_access { 189 | static inline bp2d::Coord get(bp2d::Segment const& seg) { 190 | return bp2d::getY(seg.second()); 191 | } 192 | static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) { 193 | auto p = seg.second(); bp2d::setY(p, coord); seg.second(p); 194 | } 195 | }; 196 | 197 | 198 | /* ************************************************************************** */ 199 | /* Polygon concept adaptation *********************************************** */ 200 | /* ************************************************************************** */ 201 | 202 | // Connversion between libnest2d::Orientation and order_selector /////////////// 203 | 204 | template struct ToBoostOrienation {}; 205 | 206 | template<> 207 | struct ToBoostOrienation { 208 | static const order_selector Value = clockwise; 209 | }; 210 | 211 | template<> 212 | struct ToBoostOrienation { 213 | static const order_selector Value = counterclockwise; 214 | }; 215 | 216 | static const bp2d::Orientation RealOrientation = 217 | bp2d::OrientationType::Value; 218 | 219 | // Ring implementation ///////////////////////////////////////////////////////// 220 | 221 | // Boost would refer to ClipperLib::Path (alias bp2d::PolygonImpl) as a ring 222 | template<> struct tag { 223 | using type = ring_tag; 224 | }; 225 | 226 | template<> struct point_order { 227 | static const order_selector value = 228 | ToBoostOrienation::Value; 229 | }; 230 | 231 | // All our Paths should be closed for the bin packing application 232 | template<> struct closure { 233 | static const closure_selector value = closed; 234 | }; 235 | 236 | // Polygon implementation ////////////////////////////////////////////////////// 237 | 238 | template<> struct tag { 239 | using type = polygon_tag; 240 | }; 241 | 242 | template<> struct exterior_ring { 243 | static inline bp2d::PathImpl& get(bp2d::PolygonImpl& p) { 244 | return libnest2d::shapelike::contour(p); 245 | } 246 | 247 | static inline bp2d::PathImpl const& get(bp2d::PolygonImpl const& p) { 248 | return libnest2d::shapelike::contour(p); 249 | } 250 | }; 251 | 252 | template<> struct ring_const_type { 253 | using type = const bp2d::PathImpl&; 254 | }; 255 | 256 | template<> struct ring_mutable_type { 257 | using type = bp2d::PathImpl&; 258 | }; 259 | 260 | template<> struct interior_const_type { 261 | using type = const libnest2d::THolesContainer&; 262 | }; 263 | 264 | template<> struct interior_mutable_type { 265 | using type = libnest2d::THolesContainer&; 266 | }; 267 | 268 | template<> 269 | struct interior_rings { 270 | 271 | static inline libnest2d::THolesContainer& get( 272 | bp2d::PolygonImpl& p) 273 | { 274 | return libnest2d::shapelike::holes(p); 275 | } 276 | 277 | static inline const libnest2d::THolesContainer& get( 278 | bp2d::PolygonImpl const& p) 279 | { 280 | return libnest2d::shapelike::holes(p); 281 | } 282 | }; 283 | 284 | /* ************************************************************************** */ 285 | /* MultiPolygon concept adaptation ****************************************** */ 286 | /* ************************************************************************** */ 287 | 288 | template<> struct tag { 289 | using type = multi_polygon_tag; 290 | }; 291 | 292 | } // traits 293 | } // geometry 294 | 295 | // This is an addition to the ring implementation of Polygon concept 296 | template<> 297 | struct range_value { 298 | using type = bp2d::PointImpl; 299 | }; 300 | 301 | template<> 302 | struct range_value { 303 | using type = bp2d::PolygonImpl; 304 | }; 305 | 306 | } // boost 307 | 308 | /* ************************************************************************** */ 309 | /* Algorithms *************************************************************** */ 310 | /* ************************************************************************** */ 311 | 312 | namespace libnest2d { // Now the algorithms that boost can provide... 313 | 314 | //namespace pointlike { 315 | //template<> 316 | //inline double distance(const PointImpl& p1, const PointImpl& p2 ) 317 | //{ 318 | // return boost::geometry::distance(p1, p2); 319 | //} 320 | 321 | //template<> 322 | //inline double distance(const PointImpl& p, const bp2d::Segment& seg ) 323 | //{ 324 | // return boost::geometry::distance(p, seg); 325 | //} 326 | //} 327 | 328 | namespace shapelike { 329 | // Tell libnest2d how to make string out of a ClipperPolygon object 330 | template<> 331 | inline bool intersects(const PathImpl& sh1, const PathImpl& sh2) 332 | { 333 | return boost::geometry::intersects(sh1, sh2); 334 | } 335 | 336 | // Tell libnest2d how to make string out of a ClipperPolygon object 337 | template<> 338 | inline bool intersects(const PolygonImpl& sh1, const PolygonImpl& sh2) 339 | { 340 | return boost::geometry::intersects(sh1, sh2); 341 | } 342 | 343 | // Tell libnest2d how to make string out of a ClipperPolygon object 344 | template<> 345 | inline bool intersects(const bp2d::Segment& s1, const bp2d::Segment& s2) 346 | { 347 | return boost::geometry::intersects(s1, s2); 348 | } 349 | 350 | #ifndef DISABLE_BOOST_AREA 351 | template<> 352 | inline double area(const PolygonImpl& shape, const PolygonTag&) 353 | { 354 | return boost::geometry::area(shape); 355 | } 356 | #endif 357 | 358 | template<> 359 | inline bool isInside(const PointImpl& point, const PolygonImpl& shape, 360 | const PointTag&, const PolygonTag&) 361 | { 362 | return boost::geometry::within(point, shape); 363 | } 364 | 365 | template<> 366 | inline bool isInside(const PolygonImpl& sh1, const PolygonImpl& sh2, 367 | const PolygonTag&, const PolygonTag&) 368 | { 369 | return boost::geometry::within(sh1, sh2); 370 | } 371 | 372 | template<> 373 | inline bool touches(const PolygonImpl& sh1, const PolygonImpl& sh2) 374 | { 375 | return boost::geometry::touches(sh1, sh2); 376 | } 377 | 378 | template<> 379 | inline bool touches( const PointImpl& point, const PolygonImpl& shape) 380 | { 381 | return boost::geometry::touches(point, shape); 382 | } 383 | 384 | #ifndef DISABLE_BOOST_BOUNDING_BOX 385 | 386 | template<> 387 | inline bp2d::Box boundingBox(const PathImpl& sh, const PathTag&) 388 | { 389 | bp2d::Box b; 390 | boost::geometry::envelope(sh, b); 391 | return b; 392 | } 393 | 394 | template<> 395 | inline bp2d::Box boundingBox(const bp2d::Shapes& shapes, 396 | const MultiPolygonTag&) 397 | { 398 | bp2d::Box b; 399 | boost::geometry::envelope(shapes, b); 400 | return b; 401 | } 402 | #endif 403 | 404 | #ifndef DISABLE_BOOST_CONVEX_HULL 405 | template<> 406 | inline PathImpl convexHull(const PathImpl& sh, const PathTag&) 407 | { 408 | PathImpl ret; 409 | boost::geometry::convex_hull(sh, ret); 410 | return ret; 411 | } 412 | 413 | template<> 414 | inline PolygonImpl convexHull(const TMultiShape& shapes, 415 | const MultiPolygonTag&) 416 | { 417 | PolygonImpl ret; 418 | boost::geometry::convex_hull(shapes, ret); 419 | return ret; 420 | } 421 | #endif 422 | 423 | #ifndef DISABLE_BOOST_OFFSET 424 | template<> 425 | inline void offset(PolygonImpl& sh, bp2d::Coord distance) 426 | { 427 | PolygonImpl cpy = sh; 428 | boost::geometry::buffer(cpy, sh, distance); 429 | } 430 | #endif 431 | 432 | #ifndef DISABLE_BOOST_SERIALIZE 433 | template<> inline std::string serialize( 434 | const PolygonImpl& sh, double scale) 435 | { 436 | std::stringstream ss; 437 | std::string style = "fill: none; stroke: black; stroke-width: 1px;"; 438 | 439 | using namespace boost::geometry; 440 | using Pointf = model::point; 441 | using Polygonf = model::polygon; 442 | 443 | Polygonf::ring_type ring; 444 | Polygonf::inner_container_type holes; 445 | ring.reserve(shapelike::contourVertexCount(sh)); 446 | 447 | for(auto it = shapelike::cbegin(sh); it != shapelike::cend(sh); it++) { 448 | auto& v = *it; 449 | ring.emplace_back(getX(v)*scale, getY(v)*scale); 450 | }; 451 | 452 | auto H = shapelike::holes(sh); 453 | for(PathImpl& h : H ) { 454 | Polygonf::ring_type hf; 455 | for(auto it = h.begin(); it != h.end(); it++) { 456 | auto& v = *it; 457 | hf.emplace_back(getX(v)*scale, getY(v)*scale); 458 | }; 459 | holes.emplace_back(std::move(hf)); 460 | } 461 | 462 | Polygonf poly; 463 | poly.outer() = ring; 464 | poly.inners() = holes; 465 | auto svg_data = boost::geometry::svg(poly, style); 466 | 467 | ss << svg_data << std::endl; 468 | 469 | return ss.str(); 470 | } 471 | #endif 472 | 473 | #ifndef DISABLE_BOOST_UNSERIALIZE 474 | template<> 475 | inline void unserialize( 476 | PolygonImpl& sh, 477 | const std::string& str) 478 | { 479 | } 480 | #endif 481 | 482 | template<> inline std::pair isValid(const PolygonImpl& sh) 483 | { 484 | std::string message; 485 | bool ret = boost::geometry::is_valid(sh, message); 486 | 487 | return {ret, message}; 488 | } 489 | } 490 | 491 | namespace nfp { 492 | 493 | #ifndef DISABLE_BOOST_NFP_MERGE 494 | 495 | // Warning: I could not get boost union_ to work. Geometries will overlap. 496 | template<> 497 | inline bp2d::Shapes nfp::merge(const bp2d::Shapes& shapes, 498 | const PolygonImpl& sh) 499 | { 500 | bp2d::Shapes retv; 501 | boost::geometry::union_(shapes, sh, retv); 502 | return retv; 503 | } 504 | 505 | template<> 506 | inline bp2d::Shapes nfp::merge(const bp2d::Shapes& shapes) 507 | { 508 | bp2d::Shapes retv; 509 | boost::geometry::union_(shapes, shapes.back(), retv); 510 | return retv; 511 | } 512 | #endif 513 | 514 | } 515 | 516 | 517 | } 518 | 519 | 520 | 521 | #endif // BOOST_ALG_HPP 522 | -------------------------------------------------------------------------------- /include/libnest2d/utils/metaloop.hpp: -------------------------------------------------------------------------------- 1 | #ifndef METALOOP_HPP 2 | #define METALOOP_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace libnest2d { 9 | 10 | /* ************************************************************************** */ 11 | /* C++14 std::index_sequence implementation: */ 12 | /* ************************************************************************** */ 13 | 14 | /** 15 | * \brief C++11 compatible implementation of the index_sequence type from C++14 16 | */ 17 | template struct index_sequence { 18 | using value_type = size_t; 19 | BP2D_CONSTEXPR value_type size() const { return sizeof...(Ints); } 20 | }; 21 | 22 | // A Help structure to generate the integer list 23 | template struct genSeq; 24 | 25 | // Recursive template to generate the list 26 | template struct genSeq { 27 | // Type will contain a genSeq with Nseq appended by one element 28 | using Type = typename genSeq< I - 1, I - 1, Nseq...>::Type; 29 | }; 30 | 31 | // Terminating recursion 32 | template struct genSeq<0, Nseq...> { 33 | // If I is zero, Type will contain index_sequence with the fuly generated 34 | // integer list. 35 | using Type = index_sequence; 36 | }; 37 | 38 | /// Helper alias to make an index sequence from 0 to N 39 | template using make_index_sequence = typename genSeq::Type; 40 | 41 | /// Helper alias to make an index sequence for a parameter pack 42 | template 43 | using index_sequence_for = make_index_sequence; 44 | 45 | 46 | /* ************************************************************************** */ 47 | 48 | namespace opt { 49 | 50 | using std::forward; 51 | using std::tuple; 52 | using std::get; 53 | using std::tuple_element; 54 | 55 | /** 56 | * @brief Helper class to be able to loop over a parameter pack's elements. 57 | */ 58 | class metaloop { 59 | 60 | // The implementation is based on partial struct template specializations. 61 | // Basically we need a template type that is callable and takes an integer 62 | // non-type template parameter which can be used to implement recursive calls. 63 | // 64 | // C++11 will not allow the usage of a plain template function that is why we 65 | // use struct with overloaded call operator. At the same time C++11 prohibits 66 | // partial template specialization with a non type parameter such as int. We 67 | // need to wrap that in a type (see metaloop::Int). 68 | 69 | /* 70 | * A helper alias to create integer values wrapped as a type. It is necessary 71 | * because a non type template parameter (such as int) would be prohibited in 72 | * a partial specialization. Also for the same reason we have to use a class 73 | * _Metaloop instead of a simple function as a functor. A function cannot be 74 | * partially specialized in a way that is necessary for this trick. 75 | */ 76 | template using Int = std::integral_constant; 77 | 78 | /* 79 | * Helper class to implement in-place functors. 80 | * 81 | * We want to be able to use inline functors like a lambda to keep the code 82 | * as clear as possible. 83 | */ 84 | template class MapFn { 85 | Fn&& fn_; 86 | public: 87 | 88 | // It takes the real functor that can be specified in-place but only 89 | // with C++14 because the second parameter's type will depend on the 90 | // type of the parameter pack element that is processed. In C++14 we can 91 | // specify this second parameter type as auto in the lambda parameter list. 92 | inline MapFn(Fn&& fn): fn_(forward(fn)) {} 93 | 94 | template void operator ()(T&& pack_element) { 95 | // We provide the index as the first parameter and the pack (or tuple) 96 | // element as the second parameter to the functor. 97 | fn_(N, forward(pack_element)); 98 | } 99 | }; 100 | 101 | /* 102 | * Implementation of the template loop trick. 103 | * We create a mechanism for looping over a parameter pack in compile time. 104 | * \tparam Idx is the loop index which will be decremented at each recursion. 105 | * \tparam Args The parameter pack that will be processed. 106 | * 107 | */ 108 | template 109 | class _MetaLoop {}; 110 | 111 | // Implementation for the first element of Args... 112 | template 113 | class _MetaLoop, Args...> { 114 | public: 115 | 116 | const static BP2D_CONSTEXPR int N = 0; 117 | const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1; 118 | 119 | template 120 | void run( Tup&& valtup, Fn&& fn) { 121 | MapFn {forward(fn)} (get(valtup)); 122 | } 123 | }; 124 | 125 | // Implementation for the N-th element of Args... 126 | template 127 | class _MetaLoop, Args...> { 128 | public: 129 | 130 | const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1; 131 | 132 | template 133 | void run(Tup&& valtup, Fn&& fn) { 134 | MapFn {forward(fn)} (std::get(valtup)); 135 | 136 | // Recursive call to process the next element of Args 137 | _MetaLoop, Args...> ().run(forward(valtup), 138 | forward(fn)); 139 | } 140 | }; 141 | 142 | /* 143 | * Instantiation: We must instantiate the template with the last index because 144 | * the generalized version calls the decremented instantiations recursively. 145 | * Once the instantiation with the first index is called, the terminating 146 | * version of run is called which does not call itself anymore. 147 | * 148 | * If you are utterly annoyed, at least you have learned a super crazy 149 | * functional meta-programming pattern. 150 | */ 151 | template 152 | using MetaLoop = _MetaLoop, Args...>; 153 | 154 | public: 155 | 156 | /** 157 | * \brief The final usable function template. 158 | * 159 | * This is similar to what varags was on C but in compile time C++11. 160 | * You can call: 161 | * apply(, ); 162 | * For example: 163 | * 164 | * struct mapfunc { 165 | * template void operator()(int N, T&& element) { 166 | * std::cout << "The value of the parameter "<< N <<": " 167 | * << element << std::endl; 168 | * } 169 | * }; 170 | * 171 | * apply(mapfunc(), 'a', 10, 151.545); 172 | * 173 | * C++14: 174 | * apply([](int N, auto&& element){ 175 | * std::cout << "The value of the parameter "<< N <<": " 176 | * << element << std::endl; 177 | * }, 'a', 10, 151.545); 178 | * 179 | * This yields the output: 180 | * The value of the parameter 0: a 181 | * The value of the parameter 1: 10 182 | * The value of the parameter 2: 151.545 183 | * 184 | * As an addition, the function can be called with a tuple as the second 185 | * parameter holding the arguments instead of a parameter pack. 186 | * 187 | */ 188 | template 189 | inline static void apply(Fn&& fn, Args&&...args) { 190 | MetaLoop().run(tuple(forward(args)...), 191 | forward(fn)); 192 | } 193 | 194 | /// The version of apply with a tuple rvalue reference. 195 | template 196 | inline static void apply(Fn&& fn, tuple&& tup) { 197 | MetaLoop().run(std::move(tup), forward(fn)); 198 | } 199 | 200 | /// The version of apply with a tuple lvalue reference. 201 | template 202 | inline static void apply(Fn&& fn, tuple& tup) { 203 | MetaLoop().run(tup, forward(fn)); 204 | } 205 | 206 | /// The version of apply with a tuple const reference. 207 | template 208 | inline static void apply(Fn&& fn, const tuple& tup) { 209 | MetaLoop().run(tup, forward(fn)); 210 | } 211 | 212 | /** 213 | * Call a function with its arguments encapsualted in a tuple. 214 | */ 215 | template 216 | inline static auto 217 | callFunWithTuple(Fn&& fn, Tup&& tup, index_sequence) -> 218 | decltype(fn(std::get(tup)...)) 219 | { 220 | return fn(std::get(tup)...); 221 | } 222 | 223 | }; 224 | } 225 | } 226 | 227 | #endif // METALOOP_HPP 228 | -------------------------------------------------------------------------------- /include/libnest2d/utils/rational.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIBNEST2D_RATIONAL_HPP 2 | #define LIBNEST2D_RATIONAL_HPP 3 | 4 | #include 5 | 6 | namespace libnest2d { 7 | 8 | template struct NoGCD { 9 | T operator()(const T&, const T&) { return T(1); } 10 | }; 11 | 12 | // A very simple representation of an unnormalized rational number. 13 | // The sign of the denominator is still normalized to be always positive. 14 | template , class TD = T> class Rational { 15 | T num; T den; 16 | 17 | inline void normsign() { if(den < 0) { den = -den; num = -num; } } 18 | inline void normalize() { 19 | T n = GCD()(num, den); 20 | num /= n; 21 | den /= n; 22 | } 23 | public: 24 | 25 | using BaseType = T; 26 | using DoubleType = TD; 27 | 28 | inline Rational(): num(T(0)), den(T(1)) {} 29 | 30 | inline explicit Rational(const T& n, const T& d = T(1)): num(n), den(d) 31 | { 32 | normsign(); 33 | normalize(); 34 | } 35 | 36 | inline bool operator>(const Rational& o) const { 37 | return TD(o.den) * num > TD(den) * o.num; 38 | } 39 | 40 | inline bool operator<(const Rational& o) const { 41 | return TD(o.den) * num < TD(den) * o.num; 42 | } 43 | 44 | inline bool operator==(const Rational& o) const { 45 | return TD(o.den) * num == TD(den) * o.num; 46 | } 47 | 48 | inline bool operator!=(const Rational& o) const { return !(*this == o); } 49 | 50 | inline bool operator<=(const Rational& o) const { 51 | return TD(o.den) * num <= TD(den) * o.num; 52 | } 53 | 54 | inline bool operator>=(const Rational& o) const { 55 | return TD(o.den) * num >= TD(den) * o.num; 56 | } 57 | 58 | inline bool operator< (const T& v) const { return TD(num) < TD(v) * den; } 59 | inline bool operator> (const T& v) const { return TD(num) > TD(v) * den; } 60 | inline bool operator<=(const T& v) const { return TD(num) <= TD(v) * den; } 61 | inline bool operator>=(const T& v) const { return TD(num) >= TD(v) * den; } 62 | 63 | inline Rational& operator*=(const Rational& o) { 64 | num *= o.num; den *= o.den; normalize(); 65 | return *this; 66 | } 67 | 68 | inline Rational& operator/=(const Rational& o) { 69 | num *= o.den; den *= o.num; normsign(); normalize(); return *this; 70 | } 71 | 72 | inline Rational& operator+=(const Rational& o) { 73 | den *= o.den; num = o.den * num + o.num * den; normalize(); return *this; 74 | } 75 | 76 | inline Rational& operator-=(const Rational& o) { 77 | den *= o.den; num = o.den * num - o.num * den; normalize(); return *this; 78 | } 79 | 80 | inline Rational& operator*=(const T& v) { 81 | const T gcd = GCD()(v, den); num *= v / gcd; den /= gcd; return *this; 82 | } 83 | 84 | inline Rational& operator/=(const T& v) { 85 | if(num == T{}) return *this; 86 | 87 | // Avoid overflow and preserve normalization 88 | const T gcd = GCD()(num, v); 89 | num /= gcd; 90 | den *= v / gcd; 91 | 92 | if(den < T{}) { 93 | num = -num; 94 | den = -den; 95 | } 96 | 97 | den *= v; return *this; 98 | } 99 | 100 | inline Rational& operator+=(const T& v) { num += v * den; return *this; } 101 | inline Rational& operator-=(const T& v) { num -= v * den; return *this; } 102 | 103 | inline Rational operator*(const T& v) const { auto tmp = *this; tmp *= v; return tmp; } 104 | inline Rational operator/(const T& v) const { auto tmp = *this; tmp /= v; return tmp; } 105 | inline Rational operator+(const T& v) const { auto tmp = *this; tmp += v; return tmp; } 106 | inline Rational operator-(const T& v) const { auto tmp = *this; tmp -= v; return tmp; } 107 | inline Rational operator-() const { auto tmp = *this; tmp.num = -num; return tmp; } 108 | 109 | inline T numerator() const { return num; } 110 | inline T denominator() const { return den; } 111 | }; 112 | 113 | template inline T cast(const R& r, RationalTag, ScalarTag) 114 | { 115 | return cast(r.numerator()) / cast(r.denominator()); 116 | } 117 | 118 | template inline 119 | Rational cast(const R& r, RationalTag, RationalTag) 120 | { 121 | return Rational(static_cast(r.numerator()), 122 | static_cast(r.denominator())); 123 | } 124 | 125 | template struct _NumTag> { 126 | using Type = RationalTag; 127 | }; 128 | 129 | template inline R abs(const R& r, RationalTag) 130 | { 131 | return R(abs(r.numerator()), abs(r.denumerator())); 132 | } 133 | 134 | } 135 | 136 | #endif // LIBNEST2D_RATIONAL_HPP 137 | -------------------------------------------------------------------------------- /include/libnest2d/utils/rotcalipers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ROTCALIPERS_HPP 2 | #define ROTCALIPERS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace libnest2d { 12 | 13 | template> class RotatedBox { 14 | Pt axis_; 15 | Unit bottom_ = Unit(0), right_ = Unit(0); 16 | public: 17 | 18 | RotatedBox() = default; 19 | RotatedBox(const Pt& axis, Unit b, Unit r): 20 | axis_(axis), bottom_(b), right_(r) {} 21 | 22 | inline long double area() const { 23 | long double asq = pl::magnsq(axis_); 24 | return cast(bottom_) * cast(right_) / asq; 25 | } 26 | 27 | inline long double width() const { 28 | return abs(bottom_) / std::sqrt(pl::magnsq(axis_)); 29 | } 30 | 31 | inline long double height() const { 32 | return abs(right_) / std::sqrt(pl::magnsq(axis_)); 33 | } 34 | 35 | inline Unit bottom_extent() const { return bottom_; } 36 | inline Unit right_extent() const { return right_; } 37 | inline const Pt& axis() const { return axis_; } 38 | 39 | inline Radians angleToX() const { 40 | double ret = std::atan2(getY(axis_), getX(axis_)); 41 | auto s = std::signbit(ret); 42 | if(s) ret += Pi_2; 43 | return -ret; 44 | } 45 | }; 46 | 47 | template , class Unit = TCompute> 48 | Poly removeCollinearPoints(const Poly& sh, Unit eps = Unit(0)) 49 | { 50 | Poly ret; sl::reserve(ret, sl::contourVertexCount(sh)); 51 | 52 | Pt eprev = *sl::cbegin(sh) - *std::prev(sl::cend(sh)); 53 | 54 | auto it = sl::cbegin(sh); 55 | auto itx = std::next(it); 56 | if(itx != sl::cend(sh)) while (it != sl::cend(sh)) 57 | { 58 | Pt enext = *itx - *it; 59 | 60 | auto dp = pl::dotperp(eprev, enext); 61 | if(abs(dp) > eps) sl::addVertex(ret, *it); 62 | 63 | eprev = enext; 64 | if (++itx == sl::cend(sh)) itx = sl::cbegin(sh); 65 | ++it; 66 | } 67 | 68 | return ret; 69 | } 70 | 71 | // The area of the bounding rectangle with the axis dir and support vertices 72 | template, class R = TCompute> 73 | inline R rectarea(const Pt& w, // the axis 74 | const Pt& vb, const Pt& vr, 75 | const Pt& vt, const Pt& vl) 76 | { 77 | Unit a = pl::dot(w, vr - vl); 78 | Unit b = pl::dot(-pl::perp(w), vt - vb); 79 | R m = R(a) / pl::magnsq(w); 80 | m = m * b; 81 | return m; 82 | }; 83 | 84 | template, 86 | class R = TCompute, 87 | class It = typename std::vector::const_iterator> 88 | inline R rectarea(const Pt& w, const std::array& rect) 89 | { 90 | return rectarea(w, *rect[0], *rect[1], *rect[2], *rect[3]); 91 | } 92 | 93 | // This function is only applicable to counter-clockwise oriented convex 94 | // polygons where only two points can be collinear witch each other. 95 | template , 97 | class Ratio = TCompute> 98 | RotatedBox, Unit> minAreaBoundingBox(const RawShape& sh) 99 | { 100 | using Point = TPoint; 101 | using Iterator = typename TContour::const_iterator; 102 | using pointlike::dot; using pointlike::magnsq; using pointlike::perp; 103 | 104 | // Get the first and the last vertex iterator 105 | auto first = sl::cbegin(sh); 106 | auto last = std::prev(sl::cend(sh)); 107 | 108 | // Check conditions and return undefined box if input is not sane. 109 | if(last == first) return {}; 110 | if(getX(*first) == getX(*last) && getY(*first) == getY(*last)) --last; 111 | if(last - first < 2) return {}; 112 | 113 | RawShape shcpy; // empty at this point 114 | { 115 | Point p = *first, q = *std::next(first), r = *last; 116 | 117 | // Determine orientation from first 3 vertex (should be consistent) 118 | Unit d = (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) - 119 | (Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p)); 120 | 121 | if(d > 0) { 122 | // The polygon is clockwise. A flip is needed (for now) 123 | sl::reserve(shcpy, last - first); 124 | auto it = last; while(it != first) sl::addVertex(shcpy, *it--); 125 | sl::addVertex(shcpy, *first); 126 | first = sl::cbegin(shcpy); last = std::prev(sl::cend(shcpy)); 127 | } 128 | } 129 | 130 | // Cyclic iterator increment 131 | auto inc = [&first, &last](Iterator& it) { 132 | if(it == last) it = first; else ++it; 133 | }; 134 | 135 | // Cyclic previous iterator 136 | auto prev = [&first, &last](Iterator it) { 137 | return it == first ? last : std::prev(it); 138 | }; 139 | 140 | // Cyclic next iterator 141 | auto next = [&first, &last](Iterator it) { 142 | return it == last ? first : std::next(it); 143 | }; 144 | 145 | // Establish initial (axis aligned) rectangle support verices by determining 146 | // polygon extremes: 147 | 148 | auto it = first; 149 | Iterator minX = it, maxX = it, minY = it, maxY = it; 150 | 151 | do { // Linear walk through the vertices and save the extreme positions 152 | 153 | Point v = *it, d = v - *minX; 154 | if(getX(d) < 0 || (getX(d) == 0 && getY(d) < 0)) minX = it; 155 | 156 | d = v - *maxX; 157 | if(getX(d) > 0 || (getX(d) == 0 && getY(d) > 0)) maxX = it; 158 | 159 | d = v - *minY; 160 | if(getY(d) < 0 || (getY(d) == 0 && getX(d) > 0)) minY = it; 161 | 162 | d = v - *maxY; 163 | if(getY(d) > 0 || (getY(d) == 0 && getX(d) < 0)) maxY = it; 164 | 165 | } while(++it != std::next(last)); 166 | 167 | // Update the vertices defining the bounding rectangle. The rectangle with 168 | // the smallest rotation is selected and the supporting vertices are 169 | // returned in the 'rect' argument. 170 | auto update = [&next, &inc] 171 | (const Point& w, std::array& rect) 172 | { 173 | Iterator B = rect[0], Bn = next(B); 174 | Iterator R = rect[1], Rn = next(R); 175 | Iterator T = rect[2], Tn = next(T); 176 | Iterator L = rect[3], Ln = next(L); 177 | 178 | Point b = *Bn - *B, r = *Rn - *R, t = *Tn - *T, l = *Ln - *L; 179 | Point pw = perp(w); 180 | using Pt = Point; 181 | 182 | Unit dotwpb = dot( w, b), dotwpr = dot(-pw, r); 183 | Unit dotwpt = dot(-w, t), dotwpl = dot( pw, l); 184 | Unit dw = magnsq(w); 185 | 186 | std::array angles; 187 | angles[0] = (Ratio(dotwpb) / magnsq(b)) * dotwpb; 188 | angles[1] = (Ratio(dotwpr) / magnsq(r)) * dotwpr; 189 | angles[2] = (Ratio(dotwpt) / magnsq(t)) * dotwpt; 190 | angles[3] = (Ratio(dotwpl) / magnsq(l)) * dotwpl; 191 | 192 | using AngleIndex = std::pair; 193 | std::vector A; A.reserve(4); 194 | 195 | for (size_t i = 3, j = 0; j < 4; i = j++) { 196 | if(rect[i] != rect[j] && angles[i] < dw) { 197 | auto iv = std::make_pair(angles[i], i); 198 | auto it = std::lower_bound(A.begin(), A.end(), iv, 199 | [](const AngleIndex& ai, 200 | const AngleIndex& aj) 201 | { 202 | return ai.first > aj.first; 203 | }); 204 | 205 | A.insert(it, iv); 206 | } 207 | } 208 | 209 | // The polygon is supposed to be a rectangle. 210 | if(A.empty()) return false; 211 | 212 | auto amin = A.front().first; 213 | auto imin = A.front().second; 214 | for(auto& a : A) if(a.first == amin) inc(rect[a.second]); 215 | 216 | std::rotate(rect.begin(), rect.begin() + imin, rect.end()); 217 | 218 | return true; 219 | }; 220 | 221 | Point w(1, 0); 222 | Point w_min = w; 223 | Ratio minarea((Unit(getX(*maxX)) - getX(*minX)) * 224 | (Unit(getY(*maxY)) - getY(*minY))); 225 | 226 | std::array rect = {minY, maxX, maxY, minX}; 227 | std::array minrect = rect; 228 | 229 | // An edge might be examined twice in which case the algorithm terminates. 230 | size_t c = 0, count = last - first + 1; 231 | std::vector edgemask(count, false); 232 | 233 | while(c++ < count) 234 | { 235 | // Update the support vertices, if cannot be updated, break the cycle. 236 | if(! update(w, rect)) break; 237 | 238 | size_t eidx = size_t(rect[0] - first); 239 | 240 | if(edgemask[eidx]) break; 241 | edgemask[eidx] = true; 242 | 243 | // get the unnormalized direction vector 244 | w = *rect[0] - *prev(rect[0]); 245 | 246 | // get the area of the rotated rectangle 247 | Ratio rarea = rectarea(w, rect); 248 | 249 | // Update min area and the direction of the min bounding box; 250 | if(rarea <= minarea) { w_min = w; minarea = rarea; minrect = rect; } 251 | } 252 | 253 | Unit a = dot(w_min, *minrect[1] - *minrect[3]); 254 | Unit b = dot(-perp(w_min), *minrect[2] - *minrect[0]); 255 | RotatedBox bb(w_min, a, b); 256 | 257 | return bb; 258 | } 259 | 260 | template Radians minAreaBoundingBoxRotation(const RawShape& sh) 261 | { 262 | return minAreaBoundingBox(sh).angleToX(); 263 | } 264 | 265 | 266 | } 267 | 268 | #endif // ROTCALIPERS_HPP 269 | -------------------------------------------------------------------------------- /include/libnest2d/utils/rotfinder.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ROTFINDER_HPP 2 | #define ROTFINDER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace libnest2d { 9 | 10 | template 11 | Radians findBestRotation(_Item& item) { 12 | opt::StopCriteria stopcr; 13 | stopcr.absolute_score_difference = 0.01; 14 | stopcr.max_iterations = 10000; 15 | opt::TOptimizer solver(stopcr); 16 | 17 | auto orig_rot = item.rotation(); 18 | 19 | auto result = solver.optimize_min([&item, &orig_rot](Radians rot){ 20 | item.rotation(orig_rot + rot); 21 | auto bb = item.boundingBox(); 22 | return std::sqrt(bb.height()*bb.width()); 23 | }, opt::initvals(Radians(0)), opt::bound(-Pi/2, Pi/2)); 24 | 25 | item.rotation(orig_rot); 26 | 27 | return std::get<0>(result.optimum); 28 | } 29 | 30 | template 31 | void findMinimumBoundingBoxRotations(Iterator from, Iterator to) { 32 | using V = typename std::iterator_traits::value_type; 33 | std::for_each(from, to, [](V& item){ 34 | Radians rot = findBestRotation(item); 35 | item.rotate(rot); 36 | }); 37 | } 38 | 39 | } 40 | 41 | #endif // ROTFINDER_HPP 42 | -------------------------------------------------------------------------------- /src/libnest2d.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace libnest2d { 4 | 5 | template class _Nester; 6 | template class _Nester; 7 | 8 | template std::size_t _Nester::execute( 9 | std::vector::iterator, std::vector::iterator); 10 | template std::size_t _Nester::execute( 11 | std::vector::iterator, std::vector::iterator); 12 | 13 | template std::size_t nest(std::vector::iterator from, 14 | std::vector::iterator to, 15 | const Box & bin, 16 | Coord dist, 17 | const NestConfig &cfg, 18 | NestControl ctl); 19 | 20 | template std::size_t nest(std::vector::iterator from, 21 | std::vector::iterator to, 22 | const Box & bin, 23 | Coord dist, 24 | const NestConfig &cfg, 25 | NestControl ctl); 26 | } 27 | -------------------------------------------------------------------------------- /test_package/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(nest2d_testpackage) 4 | find_package(nest2d REQUIRED) 5 | 6 | add_executable(test main.cpp) 7 | 8 | target_link_libraries(test PRIVATE nest2d::nest2d) -------------------------------------------------------------------------------- /test_package/conanfile.py: -------------------------------------------------------------------------------- 1 | from conan.tools.build import can_run 2 | from conan.tools.env import VirtualRunEnv 3 | from conan.tools.files import copy 4 | from conan import ConanFile 5 | from conan.tools.cmake import CMakeToolchain, CMakeDeps, CMake 6 | 7 | 8 | class LibNest2DTestConan(ConanFile): 9 | settings = "os", "compiler", "build_type", "arch" 10 | test_type = "explicit" 11 | 12 | def requirements(self): 13 | self.requires(self.tested_reference_str) 14 | 15 | def generate(self): 16 | venv = VirtualRunEnv(self) 17 | venv.generate() 18 | 19 | cmake = CMakeDeps(self) 20 | cmake.generate() 21 | 22 | tc = CMakeToolchain(self) 23 | tc.generate() 24 | 25 | for dep in self.dependencies.values(): 26 | for bin_dir in dep.cpp_info.bindirs: 27 | copy(self, "*.dll", src=bin_dir, dst=self.build_folder) 28 | 29 | def build(self): 30 | cmake = CMake(self) 31 | cmake.configure() 32 | cmake.build() 33 | 34 | def test(self): 35 | if can_run(self): 36 | ext = ".exe" if self.settings.os == "Windows" else "" 37 | prefix_path = "" if self.settings.os == "Windows" else "./" 38 | self.run(f"{prefix_path}test{ext}", env="conanrun", scope="run") 39 | -------------------------------------------------------------------------------- /test_package/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(void /*int argc, char **argv*/) { 4 | auto pi = libnest2d::Pi; 5 | return 0; 6 | } -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Catch2 REQUIRED) 2 | 3 | include(CTest) 4 | include(Catch) 5 | 6 | set(SRC_TEST 7 | test.cpp 8 | ../tools/svgtools.hpp 9 | ../tools/printer_parts.hpp 10 | ../tools/printer_parts.cpp 11 | ) 12 | 13 | add_executable(tests ${SRC_TEST}) 14 | target_link_libraries(tests PUBLIC project_options nest2d Catch2::Catch2WithMain) 15 | 16 | catch_discover_tests(tests 17 | TEST_PREFIX 18 | "unittests." 19 | OUTPUT_DIR 20 | . 21 | OUTPUT_PREFIX 22 | "unittests." 23 | OUTPUT_SUFFIX 24 | .xml) 25 | 26 | -------------------------------------------------------------------------------- /tools/benchmark.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Tamás Mészáros 3 | * This program is free software; you can redistribute it and/or 4 | * modify it under the terms of the GNU General Public License 5 | * as published by the Free Software Foundation; either version 2 6 | * of the License, or (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program; if not, write to the Free Software 15 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 | */ 17 | #ifndef INCLUDE_BENCHMARK_H_ 18 | #define INCLUDE_BENCHMARK_H_ 19 | 20 | #include 21 | #include 22 | 23 | /** 24 | * A class for doing benchmarks. 25 | */ 26 | class Benchmark { 27 | typedef std::chrono::high_resolution_clock Clock; 28 | typedef Clock::duration Duration; 29 | typedef Clock::time_point TimePoint; 30 | 31 | TimePoint t1, t2; 32 | Duration d; 33 | 34 | inline double to_sec(Duration d) { 35 | return d.count() * double(Duration::period::num) / Duration::period::den; 36 | } 37 | 38 | public: 39 | 40 | /** 41 | * Measure time from the moment of this call. 42 | */ 43 | void start() { t1 = Clock::now(); } 44 | 45 | /** 46 | * Measure time to the moment of this call. 47 | */ 48 | void stop() { t2 = Clock::now(); } 49 | 50 | /** 51 | * Get the time elapsed between a start() end a stop() call. 52 | * @return Returns the elapsed time in seconds. 53 | */ 54 | double getElapsedSec() { d = t2 - t1; return to_sec(d); } 55 | }; 56 | 57 | 58 | #endif /* INCLUDE_BENCHMARK_H_ */ 59 | -------------------------------------------------------------------------------- /tools/libnfpglue.cpp: -------------------------------------------------------------------------------- 1 | //#ifndef NDEBUG 2 | //#define NFP_DEBUG 3 | //#endif 4 | 5 | #include "libnfpglue.hpp" 6 | #include "tools/libnfporb/libnfporb.hpp" 7 | 8 | namespace libnest2d { 9 | 10 | namespace { 11 | inline bool vsort(const libnfporb::point_t& v1, const libnfporb::point_t& v2) 12 | { 13 | using Coord = libnfporb::coord_t; 14 | Coord x1 = v1.x_, x2 = v2.x_, y1 = v1.y_, y2 = v2.y_; 15 | auto diff = y1 - y2; 16 | #ifdef LIBNFP_USE_RATIONAL 17 | long double diffv = diff.convert_to(); 18 | #else 19 | long double diffv = diff.val(); 20 | #endif 21 | if(std::abs(diffv) <= 22 | std::numeric_limits::epsilon()) 23 | return x1 < x2; 24 | 25 | return diff < 0; 26 | } 27 | 28 | TCoord getX(const libnfporb::point_t& p) { 29 | #ifdef LIBNFP_USE_RATIONAL 30 | return p.x_.convert_to>(); 31 | #else 32 | return static_cast>(std::round(p.x_.val())); 33 | #endif 34 | } 35 | 36 | TCoord getY(const libnfporb::point_t& p) { 37 | #ifdef LIBNFP_USE_RATIONAL 38 | return p.y_.convert_to>(); 39 | #else 40 | return static_cast>(std::round(p.y_.val())); 41 | #endif 42 | } 43 | 44 | libnfporb::point_t scale(const libnfporb::point_t& p, long double factor) { 45 | #ifdef LIBNFP_USE_RATIONAL 46 | auto px = p.x_.convert_to(); 47 | auto py = p.y_.convert_to(); 48 | #else 49 | long double px = p.x_.val(); 50 | long double py = p.y_.val(); 51 | #endif 52 | return {px*factor, py*factor}; 53 | } 54 | 55 | } 56 | 57 | NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother) 58 | { 59 | namespace sl = shapelike; 60 | 61 | NfpR ret; 62 | 63 | try { 64 | libnfporb::polygon_t pstat, porb; 65 | 66 | boost::geometry::convert(sh, pstat); 67 | boost::geometry::convert(cother, porb); 68 | 69 | long double factor = 0.0000001;//libnfporb::NFP_EPSILON; 70 | long double refactor = 1.0/factor; 71 | 72 | for(auto& v : pstat.outer()) v = scale(v, factor); 73 | // std::string message; 74 | // boost::geometry::is_valid(pstat, message); 75 | // std::cout << message << std::endl; 76 | for(auto& h : pstat.inners()) for(auto& v : h) v = scale(v, factor); 77 | 78 | for(auto& v : porb.outer()) v = scale(v, factor); 79 | // message; 80 | // boost::geometry::is_valid(porb, message); 81 | // std::cout << message << std::endl; 82 | for(auto& h : porb.inners()) for(auto& v : h) v = scale(v, factor); 83 | 84 | 85 | // this can throw 86 | auto nfp = libnfporb::generateNFP(pstat, porb, true); 87 | 88 | auto &ct = sl::getContour(ret.first); 89 | ct.reserve(nfp.front().size()+1); 90 | for(auto v : nfp.front()) { 91 | v = scale(v, refactor); 92 | ct.emplace_back(getX(v), getY(v)); 93 | } 94 | ct.push_back(ct.front()); 95 | std::reverse(ct.begin(), ct.end()); 96 | 97 | auto &rholes = sl::holes(ret.first); 98 | for(size_t hidx = 1; hidx < nfp.size(); ++hidx) { 99 | if(nfp[hidx].size() >= 3) { 100 | rholes.emplace_back(); 101 | auto& h = rholes.back(); 102 | h.reserve(nfp[hidx].size()+1); 103 | 104 | for(auto& v : nfp[hidx]) { 105 | v = scale(v, refactor); 106 | h.emplace_back(getX(v), getY(v)); 107 | } 108 | h.push_back(h.front()); 109 | std::reverse(h.begin(), h.end()); 110 | } 111 | } 112 | 113 | ret.second = nfp::referenceVertex(ret.first); 114 | 115 | } catch(std::exception& e) { 116 | std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl; 117 | // auto ch_stat = ShapeLike::convexHull(sh); 118 | // auto ch_orb = ShapeLike::convexHull(cother); 119 | ret = nfp::nfpConvexOnly(sh, cother); 120 | } 121 | 122 | return ret; 123 | } 124 | 125 | NfpR nfp::NfpImpl::operator()( 126 | const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) 127 | { 128 | return _nfp(sh, cother);//nfpConvexOnly(sh, cother); 129 | } 130 | 131 | NfpR nfp::NfpImpl::operator()( 132 | const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) 133 | { 134 | return _nfp(sh, cother); 135 | } 136 | 137 | NfpR nfp::NfpImpl::operator()( 138 | const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) 139 | { 140 | return _nfp(sh, cother); 141 | } 142 | 143 | //PolygonImpl 144 | //Nfp::NfpImpl::operator()( 145 | // const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) 146 | //{ 147 | // return _nfp(sh, cother); 148 | //} 149 | 150 | //PolygonImpl 151 | //Nfp::NfpImpl::operator()( 152 | // const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) 153 | //{ 154 | // return _nfp(sh, cother); 155 | //} 156 | 157 | } 158 | -------------------------------------------------------------------------------- /tools/libnfpglue.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIBNFPGLUE_HPP 2 | #define LIBNFPGLUE_HPP 3 | 4 | #include 5 | 6 | namespace libnest2d { 7 | 8 | using NfpR = nfp::NfpResult; 9 | 10 | NfpR _nfp(const PolygonImpl& sh, const PolygonImpl& cother); 11 | 12 | template<> 13 | struct nfp::NfpImpl { 14 | NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); 15 | }; 16 | 17 | template<> 18 | struct nfp::NfpImpl { 19 | NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); 20 | }; 21 | 22 | template<> 23 | struct nfp::NfpImpl { 24 | NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); 25 | }; 26 | 27 | //template<> 28 | //struct Nfp::NfpImpl { 29 | // NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother); 30 | //}; 31 | 32 | //template<> 33 | //struct Nfp::NfpImpl { 34 | // NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother); 35 | //}; 36 | 37 | template<> struct nfp::MaxNfpLevel { 38 | static const BP2D_CONSTEXPR NfpLevel value = 39 | // NfpLevel::CONVEX_ONLY; 40 | NfpLevel::BOTH_CONCAVE; 41 | }; 42 | 43 | } 44 | 45 | 46 | #endif // LIBNFPGLUE_HPP 47 | -------------------------------------------------------------------------------- /tools/libnfporb/ORIGIN: -------------------------------------------------------------------------------- 1 | https://github.com/kallaballa/libnfp.git 2 | commit hash a5cf9f6a76ddab95567fccf629d4d099b60237d7 -------------------------------------------------------------------------------- /tools/libnfporb/README.md: -------------------------------------------------------------------------------- 1 | [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html) 2 | ##### If you give me a real good reason i might be willing to give you permission to use it under a different license for a specific application. Real good reasons include the following (non-exhausive): the greater good, educational purpose and money :) 3 | 4 | # libnfporb 5 | Implementation of a robust no-fit polygon generation in a C++ library using an orbiting approach. 6 | 7 | __Please note:__ The paper this implementation is based it on has several bad assumptions that required me to "improvise". That means the code doesn't reflect the paper anymore and is running way slower than expected. At the moment I'm working on implementing a new approach based on this paper (using minkowski sums): https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf 8 | 9 | ## Description 10 | 11 | The no-fit polygon optimization makes it possible to check for overlap (or non-overlapping touch) of two polygons with only 1 point in polygon check (by providing the set of non-overlapping placements). 12 | This library implements the orbiting approach to generate the no-fit polygon: Given two polygons A and B, A is the stationary one and B the orbiting one, B is slid as tightly as possibly around the edges of polygon A. During the orbiting a chosen reference point is tracked. By tracking the movement of the reference point a third polygon can be generated: the no-fit polygon. 13 | 14 | Once the no-fit polygon has been generated it can be used to test for overlap by only checking if the reference point is inside the NFP (overlap) outside the NFP (no overlap) or exactly on the edge of the NFP (touch). 15 | 16 | ### Examples: 17 | 18 | The polygons: 19 | 20 | ![Start of NFP](/images/start.png?raw=true) 21 | 22 | Orbiting: 23 | 24 | ![State 1](/images/next0.png?raw=true) 25 | ![State 2](/images/next1.png?raw=true) 26 | ![State 3](/images/next2.png?raw=true) 27 | ![State 4](/images/next3.png?raw=true) 28 | 29 | ![State 5](/images/next4.png?raw=true) 30 | ![State 6](/images/next5.png?raw=true) 31 | ![State 7](/images/next6.png?raw=true) 32 | ![State 8](/images/next7.png?raw=true) 33 | 34 | ![State 9](/images/next8.png?raw=true) 35 | 36 | The resulting NFP is red: 37 | 38 | ![nfp](/images/nfp.png?raw=true) 39 | 40 | Polygons can have concavities, holes, interlocks or might fit perfectly: 41 | 42 | ![concavities](/images/concavities.png?raw=true) 43 | ![hole](/images/hole.png?raw=true) 44 | ![interlock](/images/interlock.png?raw=true) 45 | ![jigsaw](/images/jigsaw.png?raw=true) 46 | 47 | ## The Approach 48 | The approch of this library is highly inspired by the scientific paper [Complete and robust no-fit polygon generation 49 | for the irregular stock cutting problem](https://pdfs.semanticscholar.org/e698/0dd78306ba7d5bb349d20c6d8f2e0aa61062.pdf) and by [Svgnest](http://svgnest.com) 50 | 51 | Note that is wasn't completely possible to implement it as suggested in the paper because it had several shortcomings that prevent complete NFP generation on some of my test cases. Especially the termination criteria (reference point returns to first point of NFP) proved to be wrong (see: test-case rect). Also tracking of used edges can't be performed as suggested in the paper since there might be situations where no edge of A is traversed (see: test-case doublecon). 52 | 53 | By default the library is using floating point as coordinate type but by defining the flag "LIBNFP_USE_RATIONAL" the library can be instructed to use infinite precision. 54 | 55 | ## Build 56 | The library has two dependencies: [Boost Geometry](http://www.boost.org/doc/libs/1_65_1/libs/geometry/doc/html/index.html) and [libgmp](https://gmplib.org). You need to install those first before building. Note that building is only required for the examples. The library itself is header-only. 57 | 58 | git clone https://github.com/kallaballa/libnfp.git 59 | cd libnfp 60 | make 61 | sudo make install 62 | 63 | ## Code Example 64 | 65 | ```c++ 66 | //uncomment next line to use infinite precision (slow) 67 | //#define LIBNFP_USE_RATIONAL 68 | #include "../src/libnfp.hpp" 69 | 70 | int main(int argc, char** argv) { 71 | using namespace libnfp; 72 | polygon_t pA; 73 | polygon_t pB; 74 | //read polygons from wkt files 75 | read_wkt_polygon(argv[1], pA); 76 | read_wkt_polygon(argv[2], pB); 77 | 78 | //generate NFP of polygon A and polygon B and check the polygons for validity. 79 | //When the third parameters is false validity check is skipped for a little performance increase 80 | nfp_t nfp = generateNFP(pA, pB, true); 81 | 82 | //write a svg containing pA, pB and NFP 83 | write_svg("nfp.svg",{pA,pB},nfp); 84 | return 0; 85 | } 86 | ``` 87 | Run the example program: 88 | 89 | examples/nfp data/crossing/A.wkt data/crossing/B.wkt 90 | -------------------------------------------------------------------------------- /tools/nfp_svgnest_glue.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NFP_SVGNEST_GLUE_HPP 2 | #define NFP_SVGNEST_GLUE_HPP 3 | 4 | #include "nfp_svgnest.hpp" 5 | 6 | #include 7 | 8 | namespace libnest2d { 9 | 10 | namespace __svgnest { 11 | 12 | //template<> struct _Tol { 13 | // static const BP2D_CONSTEXPR TCoord Value = 1000000; 14 | //}; 15 | 16 | } 17 | 18 | namespace nfp { 19 | 20 | using NfpR = NfpResult; 21 | 22 | template<> struct NfpImpl { 23 | NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { 24 | // return nfpConvexOnly(sh, cother); 25 | namespace sl = shapelike; 26 | using alg = __svgnest::_alg; 27 | 28 | auto nfp_p = alg::noFitPolygon(sl::getContour(sh), 29 | sl::getContour(cother), false, true); 30 | 31 | PolygonImpl nfp_cntr; 32 | if(!nfp_p.empty()) nfp_cntr.Contour = nfp_p.front(); 33 | return {nfp_cntr, referenceVertex(nfp_cntr)}; 34 | } 35 | }; 36 | 37 | template<> struct NfpImpl { 38 | NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { 39 | // return nfpConvexOnly(sh, cother); 40 | namespace sl = shapelike; 41 | using alg = __svgnest::_alg; 42 | 43 | auto nfp_p = alg::noFitPolygon(sl::getContour(sh), 44 | sl::getContour(cother), false, false); 45 | 46 | PolygonImpl nfp_cntr; 47 | if(!nfp_p.empty()) nfp_cntr.Contour = nfp_p.front(); 48 | return {nfp_cntr, referenceVertex(nfp_cntr)}; 49 | } 50 | }; 51 | 52 | template<> 53 | struct NfpImpl { 54 | NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) { 55 | namespace sl = shapelike; 56 | using alg = __svgnest::_alg; 57 | 58 | auto nfp_p = alg::noFitPolygon(sl::getContour(sh), 59 | sl::getContour(cother), true, false); 60 | 61 | PolygonImpl nfp_cntr; 62 | nfp_cntr.Contour = nfp_p.front(); 63 | return {nfp_cntr, referenceVertex(nfp_cntr)}; 64 | } 65 | }; 66 | 67 | template<> struct MaxNfpLevel { 68 | // static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::BOTH_CONCAVE; 69 | static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY; 70 | }; 71 | 72 | }} 73 | 74 | #endif // NFP_SVGNEST_GLUE_HPP 75 | -------------------------------------------------------------------------------- /tools/printer_parts.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRINTER_PARTS_H 2 | #define PRINTER_PARTS_H 3 | 4 | #include 5 | #include 6 | 7 | using TestData = std::vector; 8 | using TestDataEx = std::vector; 9 | 10 | extern const TestData PRINTER_PART_POLYGONS; 11 | extern const TestData STEGOSAUR_POLYGONS; 12 | extern const TestDataEx PRINTER_PART_POLYGONS_EX; 13 | 14 | #endif // PRINTER_PARTS_H 15 | -------------------------------------------------------------------------------- /tools/svgtools.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SVGTOOLS_HPP 2 | #define SVGTOOLS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace libnest2d { namespace svg { 11 | 12 | template 13 | class SVGWriter { 14 | using Item = _Item; 15 | using Coord = TCoord>; 16 | using Box = _Box>; 17 | using PackGroup = _PackGroup; 18 | 19 | public: 20 | 21 | enum OrigoLocation { 22 | TOPLEFT, 23 | BOTTOMLEFT 24 | }; 25 | 26 | struct Config { 27 | OrigoLocation origo_location; 28 | Coord mm_in_coord_units; 29 | double width, height; 30 | Config(): 31 | origo_location(BOTTOMLEFT), mm_in_coord_units(1000000), 32 | width(500), height(500) {} 33 | 34 | }; 35 | 36 | private: 37 | Config conf_; 38 | std::vector svg_layers_; 39 | bool finished_ = false; 40 | public: 41 | 42 | SVGWriter(const Config& conf = Config()): 43 | conf_(conf) {} 44 | 45 | void setSize(const Box& box) { 46 | conf_.height = static_cast(box.height()) / 47 | conf_.mm_in_coord_units; 48 | conf_.width = static_cast(box.width()) / 49 | conf_.mm_in_coord_units; 50 | } 51 | 52 | void writeItem(const Item& item) { 53 | if(svg_layers_.empty()) addLayer(); 54 | auto tsh = item.transformedShape(); 55 | if(conf_.origo_location == BOTTOMLEFT) { 56 | auto d = static_cast( 57 | std::round(conf_.height*conf_.mm_in_coord_units) ); 58 | 59 | auto& contour = shapelike::contour(tsh); 60 | for(auto& v : contour) setY(v, -getY(v) + d); 61 | 62 | auto& holes = shapelike::holes(tsh); 63 | for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d); 64 | 65 | } 66 | currentLayer() += shapelike::serialize(tsh, 67 | 1.0/conf_.mm_in_coord_units) + "\n"; 68 | } 69 | 70 | void writePackGroup(const PackGroup& result) { 71 | for(auto r : result) { 72 | addLayer(); 73 | for(Item& sh : r) { 74 | writeItem(sh); 75 | } 76 | finishLayer(); 77 | } 78 | } 79 | 80 | template void writeItems(ItemIt from, ItemIt to) { 81 | auto it = from; 82 | PackGroup pg; 83 | while(it != to) { 84 | if(it->binId() == BIN_ID_UNSET) continue; 85 | while(pg.size() <= size_t(it->binId())) pg.emplace_back(); 86 | pg[it->binId()].emplace_back(*it); 87 | ++it; 88 | } 89 | writePackGroup(pg); 90 | } 91 | 92 | void addLayer() { 93 | svg_layers_.emplace_back(header()); 94 | finished_ = false; 95 | } 96 | 97 | void finishLayer() { 98 | currentLayer() += "\n\n"; 99 | finished_ = true; 100 | } 101 | 102 | void save(const std::string& filepath) { 103 | size_t lyrc = svg_layers_.size() > 1? 1 : 0; 104 | size_t last = svg_layers_.size() > 1? svg_layers_.size() : 0; 105 | 106 | for(auto& lyr : svg_layers_) { 107 | std::fstream out(filepath + (lyrc > 0? std::to_string(lyrc) : "") + 108 | ".svg", std::fstream::out); 109 | if(out.is_open()) out << lyr; 110 | if(lyrc == last && !finished_) out << "\n\n"; 111 | out.flush(); out.close(); lyrc++; 112 | }; 113 | } 114 | 115 | private: 116 | 117 | std::string& currentLayer() { return svg_layers_.back(); } 118 | 119 | const std::string header() const { 120 | std::string svg_header = 121 | R"raw( 122 | 123 | )raw"; 126 | return svg_header; 127 | } 128 | 129 | }; 130 | 131 | } 132 | } 133 | 134 | #endif // SVGTOOLS_HPP 135 | --------------------------------------------------------------------------------