Fix cmake with msvc generator.

* Remove unused libnest2d files. Make it use the global build script targets.
* Modify FindTBB to address multi-config builds and take care of __TBB_NO_IMPLICIT_LINKAGE
* Move FindNLopt to project common cmake module dir
* Rename libnest.hpp to nester.hpp and libnest.h to libnest.hpp 
* Clean up common test suite build scripts
This commit is contained in:
tamasmeszaros 2019-10-14 12:50:08 +02:00
parent 62b7892fa1
commit fbc174ad06
30 changed files with 1536 additions and 5910 deletions

View File

@ -291,14 +291,14 @@ if(SLIC3R_STATIC)
endif()
set(TBB_DEBUG 1)
find_package(TBB REQUIRED)
include_directories(${TBB_INCLUDE_DIRS})
add_definitions(${TBB_DEFINITIONS})
if(MSVC)
# Suppress implicit linking of the TBB libraries by the Visual Studio compiler.
add_definitions(-D__TBB_NO_IMPLICIT_LINKAGE)
endif()
# include_directories(${TBB_INCLUDE_DIRS})
# add_definitions(${TBB_DEFINITIONS})
# if(MSVC)
# # Suppress implicit linking of the TBB libraries by the Visual Studio compiler.
# add_definitions(-D__TBB_NO_IMPLICIT_LINKAGE)
# endif()
# The Intel TBB library will use the std::exception_ptr feature of C++11.
add_definitions(-DTBB_USE_CAPTURED_EXCEPTION=0)
# add_definitions(-DTBB_USE_CAPTURED_EXCEPTION=0)
find_package(CURL REQUIRED)
include_directories(${CURL_INCLUDE_DIRS})
@ -375,6 +375,8 @@ add_custom_target(pot
COMMENT "Generate pot file from strings in the source tree"
)
find_package(NLopt 1.4 REQUIRED)
# libslic3r, PrusaSlicer GUI and the PrusaSlicer executable.
add_subdirectory(src)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT PrusaSlicer_app_console)

View File

@ -21,8 +21,7 @@
set(NLopt_FOUND FALSE)
set(NLopt_ERROR_REASON "")
set(NLopt_DEFINITIONS "")
set(NLopt_LIBS)
unset(NLopt_LIBS CACHE)
set(NLopt_DIR $ENV{NLOPT})
if(NOT NLopt_DIR)
@ -48,7 +47,6 @@ if(NOT NLopt_DIR)
set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Cannot find NLopt header file '${_NLopt_HEADER_FILE_NAME}'.")
endif()
unset(_NLopt_HEADER_FILE_NAME)
unset(_NLopt_HEADER_FILE)
if(NOT NLopt_FOUND)
set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} NLopt not found in system directories (and environment variable NLOPT is not set).")
@ -56,7 +54,7 @@ if(NOT NLopt_DIR)
get_filename_component(NLopt_INCLUDE_DIR ${_NLopt_HEADER_FILE} DIRECTORY )
endif()
unset(_NLopt_HEADER_FILE CACHE)
else()
@ -95,7 +93,7 @@ else()
set(NLopt_ERROR_REASON "${NLopt_ERROR_REASON} Cannot find NLopt header file '${_NLopt_HEADER_FILE_NAME}' in '${NLopt_INCLUDE_DIR}'.")
endif()
unset(_NLopt_HEADER_FILE_NAME)
unset(_NLopt_HEADER_FILE)
unset(_NLopt_HEADER_FILE CACHE)
endif()
@ -114,10 +112,10 @@ if(NLopt_FOUND)
message(STATUS "Found NLopt in '${NLopt_DIR}'.")
message(STATUS "Using NLopt include directory '${NLopt_INCLUDE_DIR}'.")
message(STATUS "Using NLopt library '${NLopt_LIBS}'.")
add_library(Nlopt::Nlopt INTERFACE IMPORTED)
set_target_properties(Nlopt::Nlopt PROPERTIES INTERFACE_LINK_LIBRARIES ${NLopt_LIBS})
set_target_properties(Nlopt::Nlopt PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${NLopt_INCLUDE_DIR})
set_target_properties(Nlopt::Nlopt PROPERTIES INTERFACE_COMPILE_DEFINITIONS "${NLopt_DEFINITIONS}")
add_library(NLopt::nlopt INTERFACE IMPORTED)
set_target_properties(NLopt::nlopt PROPERTIES INTERFACE_LINK_LIBRARIES ${NLopt_LIBS})
set_target_properties(NLopt::nlopt PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${NLopt_INCLUDE_DIR})
set_target_properties(NLopt::nlopt PROPERTIES INTERFACE_COMPILE_DEFINITIONS "${NLopt_DEFINITIONS}")
# target_link_libraries(Nlopt::Nlopt INTERFACE ${NLopt_LIBS})
# target_include_directories(Nlopt::Nlopt INTERFACE ${NLopt_INCLUDE_DIR})
# target_compile_definitions(Nlopt::Nlopt INTERFACE ${NLopt_DEFINITIONS})

View File

@ -250,26 +250,23 @@ if(NOT TBB_FOUND)
endif()
endforeach()
unset(TBB_STATIC_SUFFIX)
##################################
# Set compile flags and libraries
##################################
set(TBB_DEFINITIONS_RELEASE "")
set(TBB_DEFINITIONS_DEBUG "-DTBB_USE_DEBUG=1")
set(TBB_DEFINITIONS_DEBUG "TBB_USE_DEBUG=1")
if(TBB_LIBRARIES_${TBB_BUILD_TYPE})
set(TBB_DEFINITIONS "${TBB_DEFINITIONS_${TBB_BUILD_TYPE}}")
set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}")
elseif(TBB_LIBRARIES_RELEASE)
set(TBB_DEFINITIONS "${TBB_DEFINITIONS_RELEASE}")
set(TBB_LIBRARIES "${TBB_LIBRARIES_RELEASE}")
elseif(TBB_LIBRARIES_DEBUG)
set(TBB_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}")
set(TBB_LIBRARIES "${TBB_LIBRARIES_DEBUG}")
endif()
if (MSVC AND TBB_STATIC)
set(TBB_DEFINITIONS __TBB_NO_IMPLICIT_LINKAGE)
endif ()
unset (TBB_STATIC_SUFFIX)
find_package_handle_standard_args(TBB
REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES
HANDLE_COMPONENTS
@ -280,25 +277,18 @@ if(NOT TBB_FOUND)
##################################
if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND)
add_library(tbb UNKNOWN IMPORTED)
set_target_properties(tbb PROPERTIES
add_library(TBB::tbb UNKNOWN IMPORTED)
set_target_properties(TBB::tbb PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS}
IMPORTED_LOCATION ${TBB_LIBRARIES})
if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG)
set_target_properties(tbb PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "$<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:TBB_USE_DEBUG=1>"
set_target_properties(TBB::tbb PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS};$<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:${TBB_DEFINITIONS_DEBUG}>;$<$<CONFIG:Release>:${TBB_DEFINITIONS_RELEASE}>"
IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG}
IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_RELEASE}
IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE}
IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE}
)
elseif(TBB_LIBRARIES_RELEASE)
set_target_properties(tbb PROPERTIES IMPORTED_LOCATION ${TBB_LIBRARIES_RELEASE})
else()
set_target_properties(tbb PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}"
IMPORTED_LOCATION ${TBB_LIBRARIES_DEBUG}
)
endif()
endif()

View File

@ -16,7 +16,6 @@ add_subdirectory(semver)
add_subdirectory(libigl)
# Adding libnest2d project for bin packing...
set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d")
add_subdirectory(libnest2d)
add_subdirectory(libslic3r)

View File

@ -1,106 +1,31 @@
cmake_minimum_required(VERSION 3.0)
project(Libnest2D)
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
# Update if necessary
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long ")
endif()
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED)
# Add our own cmake module path.
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules/)
option(LIBNEST2D_HEADER_ONLY "If enabled static library will not be built." ON)
set(GEOMETRY_BACKENDS clipper boost eigen)
set(LIBNEST2D_GEOMETRIES clipper CACHE STRING "Geometry backend")
set_property(CACHE LIBNEST2D_GEOMETRIES PROPERTY STRINGS ${GEOMETRY_BACKENDS})
list(FIND GEOMETRY_BACKENDS ${LIBNEST2D_GEOMETRIES} GEOMETRY_TYPE)
if(${GEOMETRY_TYPE} EQUAL -1)
message(FATAL_ERROR "Option ${LIBNEST2D_GEOMETRIES} not supported, valid entries are ${GEOMETRY_BACKENDS}")
endif()
set(OPTIMIZERS nlopt optimlib)
set(LIBNEST2D_OPTIMIZER nlopt CACHE STRING "Optimization backend")
set_property(CACHE LIBNEST2D_OPTIMIZER PROPERTY STRINGS ${OPTIMIZERS})
list(FIND OPTIMIZERS ${LIBNEST2D_OPTIMIZER} OPTIMIZER_TYPE)
if(${OPTIMIZER_TYPE} EQUAL -1)
message(FATAL_ERROR "Option ${LIBNEST2D_OPTIMIZER} not supported, valid entries are ${OPTIMIZERS}")
endif()
add_library(libnest2d INTERFACE)
set(SRC_DIR ${PROJECT_SOURCE_DIR}/include)
set(LIBNEST2D_SRCFILES
${SRC_DIR}/libnest2d/libnest2d.hpp # Templates only
${SRC_DIR}/libnest2d/geometry_traits.hpp
${SRC_DIR}/libnest2d/geometry_traits_nfp.hpp
${SRC_DIR}/libnest2d/common.hpp
${SRC_DIR}/libnest2d/optimizer.hpp
${SRC_DIR}/libnest2d/utils/metaloop.hpp
${SRC_DIR}/libnest2d/utils/rotfinder.hpp
${SRC_DIR}/libnest2d/utils/rotcalipers.hpp
${SRC_DIR}/libnest2d/utils/bigint.hpp
${SRC_DIR}/libnest2d/utils/rational.hpp
${SRC_DIR}/libnest2d/placers/placer_boilerplate.hpp
${SRC_DIR}/libnest2d/placers/bottomleftplacer.hpp
${SRC_DIR}/libnest2d/placers/nfpplacer.hpp
${SRC_DIR}/libnest2d/selections/selection_boilerplate.hpp
${SRC_DIR}/libnest2d/selections/filler.hpp
${SRC_DIR}/libnest2d/selections/firstfit.hpp
${SRC_DIR}/libnest2d/selections/djd_heuristic.hpp
include/libnest2d/libnest2d.hpp
include/libnest2d/nester.hpp
include/libnest2d/geometry_traits.hpp
include/libnest2d/geometry_traits_nfp.hpp
include/libnest2d/common.hpp
include/libnest2d/optimizer.hpp
include/libnest2d/utils/metaloop.hpp
include/libnest2d/utils/rotfinder.hpp
include/libnest2d/utils/rotcalipers.hpp
include/libnest2d/placers/placer_boilerplate.hpp
include/libnest2d/placers/bottomleftplacer.hpp
include/libnest2d/placers/nfpplacer.hpp
include/libnest2d/selections/selection_boilerplate.hpp
#include/libnest2d/selections/filler.hpp
include/libnest2d/selections/firstfit.hpp
#include/libnest2d/selections/djd_heuristic.hpp
include/libnest2d/backends/clipper/geometries.hpp
include/libnest2d/backends/clipper/clipper_polygon.hpp
include/libnest2d/optimizers/nlopt/nlopt_boilerplate.hpp
include/libnest2d/optimizers/nlopt/simplex.hpp
include/libnest2d/optimizers/nlopt/subplex.hpp
include/libnest2d/optimizers/nlopt/genetic.hpp
src/libnest2d.cpp
)
set(TBB_STATIC ON)
find_package(TBB QUIET)
if(TBB_FOUND)
message(STATUS "Parallelization with Intel TBB")
target_include_directories(libnest2d INTERFACE ${TBB_INCLUDE_DIRS})
target_compile_definitions(libnest2d INTERFACE ${TBB_DEFINITIONS} -DUSE_TBB)
if(MSVC)
# Suppress implicit linking of the TBB libraries by the Visual Studio compiler.
target_compile_definitions(libnest2d INTERFACE -D__TBB_NO_IMPLICIT_LINKAGE)
endif()
# The Intel TBB library will use the std::exception_ptr feature of C++11.
target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0)
add_library(libnest2d ${LIBNEST2D_SRCFILES})
find_package(Threads REQUIRED)
target_link_libraries(libnest2d INTERFACE
tbb # VS debug mode needs linking this way:
# ${TBB_LIBRARIES}
${CMAKE_DL_LIBS}
Threads::Threads
)
else()
find_package(OpenMP QUIET)
if(OpenMP_CXX_FOUND)
message(STATUS "Parallelization with OpenMP")
target_include_directories(libnest2d INTERFACE OpenMP::OpenMP_CXX)
target_link_libraries(libnest2d INTERFACE OpenMP::OpenMP_CXX)
else()
message("Parallelization with C++11 threads")
find_package(Threads REQUIRED)
target_link_libraries(libnest2d INTERFACE Threads::Threads)
endif()
endif()
add_subdirectory(${SRC_DIR}/libnest2d/backends/${LIBNEST2D_GEOMETRIES})
target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_GEOMETRIES}Backend)
add_subdirectory(${SRC_DIR}/libnest2d/optimizers/${LIBNEST2D_OPTIMIZER})
target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_OPTIMIZER}Optimizer)
# target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES})
target_include_directories(libnest2d INTERFACE ${SRC_DIR})
if(NOT LIBNEST2D_HEADER_ONLY)
set(LIBNAME libnest2d_${LIBNEST2D_GEOMETRIES}_${LIBNEST2D_OPTIMIZER})
add_library(${LIBNAME} ${PROJECT_SOURCE_DIR}/src/libnest2d.cpp)
target_link_libraries(${LIBNAME} PUBLIC libnest2d)
target_compile_definitions(${LIBNAME} PUBLIC LIBNEST2D_STATIC)
endif()
target_include_directories(libnest2d PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
target_link_libraries(libnest2d PUBLIC clipper NLopt::nlopt TBB::tbb Boost::boost)
target_compile_definitions(libnest2d PUBLIC LIBNEST2D_STATIC LIBNEST2D_OPTIMIZER_nlopt LIBNEST2D_GEOMETRIES_clipper)

View File

@ -1,35 +0,0 @@
include(DownloadProject)
if (CMAKE_VERSION VERSION_LESS 3.2)
set(UPDATE_DISCONNECTED_IF_AVAILABLE "")
else()
set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1")
endif()
set(URL_NLOPT "https://github.com/stevengj/nlopt.git"
CACHE STRING "Location of the nlopt git repository")
# set(NLopt_DIR ${CMAKE_BINARY_DIR}/nlopt)
include(DownloadProject)
download_project( PROJ nlopt
GIT_REPOSITORY ${URL_NLOPT}
GIT_TAG v2.5.0
# CMAKE_CACHE_ARGS -DBUILD_SHARED_LIBS:BOOL=OFF -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${NLopt_DIR}
${UPDATE_DISCONNECTED_IF_AVAILABLE}
)
set(SHARED_LIBS_STATE BUILD_SHARED_LIBS)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(NLOPT_PYTHON OFF CACHE BOOL "" FORCE)
set(NLOPT_OCTAVE OFF CACHE BOOL "" FORCE)
set(NLOPT_MATLAB OFF CACHE BOOL "" FORCE)
set(NLOPT_GUILE OFF CACHE BOOL "" FORCE)
set(NLOPT_SWIG OFF CACHE BOOL "" FORCE)
set(NLOPT_LINK_PYTHON OFF CACHE BOOL "" FORCE)
add_subdirectory(${nlopt_SOURCE_DIR} ${nlopt_BINARY_DIR})
set(NLopt_LIBS nlopt)
set(NLopt_INCLUDE_DIR ${nlopt_BINARY_DIR}
${nlopt_BINARY_DIR}/src/api)
set(SHARED_LIBS_STATE ${SHARED_STATE})

View File

@ -1,17 +0,0 @@
# Distributed under the OSI-approved MIT License. See accompanying
# file LICENSE or https://github.com/Crascit/DownloadProject for details.
cmake_minimum_required(VERSION 2.8.2)
project(${DL_ARGS_PROJ}-download NONE)
include(ExternalProject)
ExternalProject_Add(${DL_ARGS_PROJ}-download
${DL_ARGS_UNPARSED_ARGUMENTS}
SOURCE_DIR "${DL_ARGS_SOURCE_DIR}"
BINARY_DIR "${DL_ARGS_BINARY_DIR}"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)

View File

@ -1,182 +0,0 @@
# Distributed under the OSI-approved MIT License. See accompanying
# file LICENSE or https://github.com/Crascit/DownloadProject for details.
#
# MODULE: DownloadProject
#
# PROVIDES:
# download_project( PROJ projectName
# [PREFIX prefixDir]
# [DOWNLOAD_DIR downloadDir]
# [SOURCE_DIR srcDir]
# [BINARY_DIR binDir]
# [QUIET]
# ...
# )
#
# Provides the ability to download and unpack a tarball, zip file, git repository,
# etc. at configure time (i.e. when the cmake command is run). How the downloaded
# and unpacked contents are used is up to the caller, but the motivating case is
# to download source code which can then be included directly in the build with
# add_subdirectory() after the call to download_project(). Source and build
# directories are set up with this in mind.
#
# The PROJ argument is required. The projectName value will be used to construct
# the following variables upon exit (obviously replace projectName with its actual
# value):
#
# projectName_SOURCE_DIR
# projectName_BINARY_DIR
#
# The SOURCE_DIR and BINARY_DIR arguments are optional and would not typically
# need to be provided. They can be specified if you want the downloaded source
# and build directories to be located in a specific place. The contents of
# projectName_SOURCE_DIR and projectName_BINARY_DIR will be populated with the
# locations used whether you provide SOURCE_DIR/BINARY_DIR or not.
#
# The DOWNLOAD_DIR argument does not normally need to be set. It controls the
# location of the temporary CMake build used to perform the download.
#
# The PREFIX argument can be provided to change the base location of the default
# values of DOWNLOAD_DIR, SOURCE_DIR and BINARY_DIR. If all of those three arguments
# are provided, then PREFIX will have no effect. The default value for PREFIX is
# CMAKE_BINARY_DIR.
#
# The QUIET option can be given if you do not want to show the output associated
# with downloading the specified project.
#
# In addition to the above, any other options are passed through unmodified to
# ExternalProject_Add() to perform the actual download, patch and update steps.
# The following ExternalProject_Add() options are explicitly prohibited (they
# are reserved for use by the download_project() command):
#
# CONFIGURE_COMMAND
# BUILD_COMMAND
# INSTALL_COMMAND
# TEST_COMMAND
#
# Only those ExternalProject_Add() arguments which relate to downloading, patching
# and updating of the project sources are intended to be used. Also note that at
# least one set of download-related arguments are required.
#
# If using CMake 3.2 or later, the UPDATE_DISCONNECTED option can be used to
# prevent a check at the remote end for changes every time CMake is run
# after the first successful download. See the documentation of the ExternalProject
# module for more information. It is likely you will want to use this option if it
# is available to you. Note, however, that the ExternalProject implementation contains
# bugs which result in incorrect handling of the UPDATE_DISCONNECTED option when
# using the URL download method or when specifying a SOURCE_DIR with no download
# method. Fixes for these have been created, the last of which is scheduled for
# inclusion in CMake 3.8.0. Details can be found here:
#
# https://gitlab.kitware.com/cmake/cmake/commit/bdca68388bd57f8302d3c1d83d691034b7ffa70c
# https://gitlab.kitware.com/cmake/cmake/issues/16428
#
# If you experience build errors related to the update step, consider avoiding
# the use of UPDATE_DISCONNECTED.
#
# EXAMPLE USAGE:
#
# include(DownloadProject)
# download_project(PROJ googletest
# GIT_REPOSITORY https://github.com/google/googletest.git
# GIT_TAG master
# UPDATE_DISCONNECTED 1
# QUIET
# )
#
# add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})
#
#========================================================================================
set(_DownloadProjectDir "${CMAKE_CURRENT_LIST_DIR}")
include(CMakeParseArguments)
function(download_project)
set(options QUIET)
set(oneValueArgs
PROJ
PREFIX
DOWNLOAD_DIR
SOURCE_DIR
BINARY_DIR
# Prevent the following from being passed through
CONFIGURE_COMMAND
BUILD_COMMAND
INSTALL_COMMAND
TEST_COMMAND
)
set(multiValueArgs "")
cmake_parse_arguments(DL_ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Hide output if requested
if (DL_ARGS_QUIET)
set(OUTPUT_QUIET "OUTPUT_QUIET")
else()
unset(OUTPUT_QUIET)
message(STATUS "Downloading/updating ${DL_ARGS_PROJ}")
endif()
# Set up where we will put our temporary CMakeLists.txt file and also
# the base point below which the default source and binary dirs will be.
# The prefix must always be an absolute path.
if (NOT DL_ARGS_PREFIX)
set(DL_ARGS_PREFIX "${CMAKE_BINARY_DIR}")
else()
get_filename_component(DL_ARGS_PREFIX "${DL_ARGS_PREFIX}" ABSOLUTE
BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
endif()
if (NOT DL_ARGS_DOWNLOAD_DIR)
set(DL_ARGS_DOWNLOAD_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-download")
endif()
# Ensure the caller can know where to find the source and build directories
if (NOT DL_ARGS_SOURCE_DIR)
set(DL_ARGS_SOURCE_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-src")
endif()
if (NOT DL_ARGS_BINARY_DIR)
set(DL_ARGS_BINARY_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-build")
endif()
set(${DL_ARGS_PROJ}_SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" PARENT_SCOPE)
set(${DL_ARGS_PROJ}_BINARY_DIR "${DL_ARGS_BINARY_DIR}" PARENT_SCOPE)
# The way that CLion manages multiple configurations, it causes a copy of
# the CMakeCache.txt to be copied across due to it not expecting there to
# be a project within a project. This causes the hard-coded paths in the
# cache to be copied and builds to fail. To mitigate this, we simply
# remove the cache if it exists before we configure the new project. It
# is safe to do so because it will be re-generated. Since this is only
# executed at the configure step, it should not cause additional builds or
# downloads.
file(REMOVE "${DL_ARGS_DOWNLOAD_DIR}/CMakeCache.txt")
# Create and build a separate CMake project to carry out the download.
# If we've already previously done these steps, they will not cause
# anything to be updated, so extra rebuilds of the project won't occur.
# Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project
# has this set to something not findable on the PATH.
configure_file("${_DownloadProjectDir}/DownloadProject.CMakeLists.cmake.in"
"${DL_ARGS_DOWNLOAD_DIR}/CMakeLists.txt")
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}"
-D "CMAKE_MAKE_PROGRAM:FILE=${CMAKE_MAKE_PROGRAM}"
.
RESULT_VARIABLE result
${OUTPUT_QUIET}
WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}"
)
if(result)
message(FATAL_ERROR "CMake step for ${DL_ARGS_PROJ} failed: ${result}")
endif()
execute_process(COMMAND ${CMAKE_COMMAND} --build .
RESULT_VARIABLE result
${OUTPUT_QUIET}
WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}"
)
if(result)
message(FATAL_ERROR "Build step for ${DL_ARGS_PROJ} failed: ${result}")
endif()
endfunction()

View File

@ -1,58 +0,0 @@
# Find Clipper library (http://www.angusj.com/delphi/clipper.php).
# The following variables are set
#
# CLIPPER_FOUND
# CLIPPER_INCLUDE_DIRS
# CLIPPER_LIBRARIES
#
# It searches the environment variable $CLIPPER_PATH automatically.
FIND_PATH(CLIPPER_INCLUDE_DIRS clipper.hpp
$ENV{CLIPPER_PATH}
$ENV{CLIPPER_PATH}/cpp/
$ENV{CLIPPER_PATH}/include/
$ENV{CLIPPER_PATH}/include/polyclipping/
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/include/
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/include/polyclipping/
${CMAKE_PREFIX_PATH}/include/polyclipping
${CMAKE_PREFIX_PATH}/include/
/opt/local/include/
/opt/local/include/polyclipping/
/usr/local/include/
/usr/local/include/polyclipping/
/usr/include
/usr/include/polyclipping/)
FIND_LIBRARY(CLIPPER_LIBRARIES polyclipping
$ENV{CLIPPER_PATH}
$ENV{CLIPPER_PATH}/cpp/
$ENV{CLIPPER_PATH}/cpp/build/
$ENV{CLIPPER_PATH}/lib/
$ENV{CLIPPER_PATH}/lib/polyclipping/
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/lib/
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/lib/polyclipping/
${CMAKE_PREFIX_PATH}/lib/
${CMAKE_PREFIX_PATH}/lib/polyclipping/
/opt/local/lib/
/opt/local/lib/polyclipping/
/usr/local/lib/
/usr/local/lib/polyclipping/
/usr/lib/polyclipping)
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Clipper
"Clipper library cannot be found. Consider set CLIPPER_PATH environment variable"
CLIPPER_INCLUDE_DIRS
CLIPPER_LIBRARIES)
MARK_AS_ADVANCED(
CLIPPER_INCLUDE_DIRS
CLIPPER_LIBRARIES)
if(CLIPPER_FOUND)
add_library(Clipper::Clipper INTERFACE IMPORTED)
set_target_properties(Clipper::Clipper PROPERTIES INTERFACE_LINK_LIBRARIES ${CLIPPER_LIBRARIES})
set_target_properties(Clipper::Clipper PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CLIPPER_INCLUDE_DIRS})
#target_link_libraries(Clipper::Clipper INTERFACE ${CLIPPER_LIBRARIES})
#target_include_directories(Clipper::Clipper INTERFACE ${CLIPPER_INCLUDE_DIRS})
endif()

View File

@ -1,134 +0,0 @@
#ifndef LIBNEST2D_H
#define LIBNEST2D_H
// The type of backend should be set conditionally by the cmake configuriation
// for now we set it statically to clipper backend
#ifdef LIBNEST2D_BACKEND_CLIPPER
#include <libnest2d/backends/clipper/geometries.hpp>
#endif
#ifdef LIBNEST2D_OPTIMIZER_NLOPT
// We include the stock optimizers for local and global optimization
#include <libnest2d/optimizers/nlopt/subplex.hpp> // Local subplex for NfpPlacer
#include <libnest2d/optimizers/nlopt/genetic.hpp> // Genetic for min. bounding box
#endif
#include <libnest2d/libnest2d.hpp>
#include <libnest2d/placers/bottomleftplacer.hpp>
#include <libnest2d/placers/nfpplacer.hpp>
#include <libnest2d/selections/firstfit.hpp>
#include <libnest2d/selections/filler.hpp>
#include <libnest2d/selections/djd_heuristic.hpp>
namespace libnest2d {
using Point = PointImpl;
using Coord = TCoord<PointImpl>;
using Box = _Box<PointImpl>;
using Segment = _Segment<PointImpl>;
using Circle = _Circle<PointImpl>;
using Item = _Item<PolygonImpl>;
using Rectangle = _Rectangle<PolygonImpl>;
using PackGroup = _PackGroup<PolygonImpl>;
using FillerSelection = selections::_FillerSelection<PolygonImpl>;
using FirstFitSelection = selections::_FirstFitSelection<PolygonImpl>;
using DJDHeuristic = selections::_DJDHeuristic<PolygonImpl>;
template<class Bin> // Generic placer for arbitrary bin types
using _NfpPlacer = placers::_NofitPolyPlacer<PolygonImpl, Bin>;
// NfpPlacer is with Box bin
using NfpPlacer = _NfpPlacer<Box>;
// This supports only box shaped bins
using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>;
#ifdef LIBNEST2D_STATIC
extern template class Nester<NfpPlacer, FirstFitSelection>;
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
extern template std::size_t Nester<NfpPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
extern template std::size_t Nester<BottomLeftPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
#endif
template<class Placer = NfpPlacer, class Selector = FirstFitSelection>
struct NestConfig {
typename Placer::Config placer_config;
typename Selector::Config selector_config;
using Placement = typename Placer::Config;
using Selection = typename Selector::Config;
NestConfig() = default;
NestConfig(const typename Placer::Config &cfg) : placer_config{cfg} {}
NestConfig(const typename Selector::Config &cfg) : selector_config{cfg} {}
NestConfig(const typename Placer::Config & pcfg,
const typename Selector::Config &scfg)
: placer_config{pcfg}, selector_config{scfg} {}
};
struct NestControl {
ProgressFunction progressfn;
StopCondition stopcond = []{ return false; };
NestControl() = default;
NestControl(ProgressFunction pr) : progressfn{std::move(pr)} {}
NestControl(StopCondition sc) : stopcond{std::move(sc)} {}
NestControl(ProgressFunction pr, StopCondition sc)
: progressfn{std::move(pr)}, stopcond{std::move(sc)}
{}
};
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Iterator = std::vector<Item>::iterator>
std::size_t nest(Iterator from, Iterator to,
const typename Placer::BinType & bin,
Coord dist = 0,
const NestConfig<Placer, Selector> &cfg = {},
NestControl ctl = {})
{
_Nester<Placer, Selector> nester{bin, dist, cfg.placer_config, cfg.selector_config};
if(ctl.progressfn) nester.progressIndicator(ctl.progressfn);
if(ctl.stopcond) nester.stopCondition(ctl.stopcond);
return nester.execute(from, to);
}
#ifdef LIBNEST2D_STATIC
extern template class Nester<NfpPlacer, FirstFitSelection>;
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
extern template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator from to,
const Box & bin,
Coord dist,
const NestConfig<NfpPlacer, FirstFitSelection> &cfg,
NestControl ctl);
extern template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator from to,
const Box & bin,
Coord dist,
const NestConfig<BottomLeftPlacer, FirstFitSelection> &cfg,
NestControl ctl);
#endif
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Container = std::vector<Item>>
std::size_t nest(Container&& cont,
const typename Placer::BinType & bin,
Coord dist = 0,
const NestConfig<Placer, Selector> &cfg = {},
NestControl ctl = {})
{
return nest<Placer, Selector>(cont.begin(), cont.end(), bin, dist, cfg, ctl);
}
}
#endif // LIBNEST2D_H

View File

@ -1,73 +0,0 @@
if(NOT TARGET clipper) # If there is a clipper target in the parent project we are good to go.
find_package(Clipper 6.1)
if(NOT CLIPPER_FOUND)
find_package(Subversion QUIET)
if(Subversion_FOUND)
set(URL_CLIPPER "https://svn.code.sf.net/p/polyclipping/code/trunk/cpp"
CACHE STRING "Clipper source code repository location.")
message(STATUS "Clipper not found so it will be downloaded.")
# Silently download and build the library in the build dir
if (CMAKE_VERSION VERSION_LESS 3.2)
set(UPDATE_DISCONNECTED_IF_AVAILABLE "")
else()
set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1")
endif()
include(DownloadProject)
download_project( PROJ clipper_library
SVN_REPOSITORY ${URL_CLIPPER}
SVN_REVISION -r540
#SOURCE_SUBDIR cpp
INSTALL_COMMAND ""
CONFIGURE_COMMAND "" # Not working, I will just add the source files
${UPDATE_DISCONNECTED_IF_AVAILABLE}
)
# This is not working and I dont have time to fix it
# add_subdirectory(${clipper_library_SOURCE_DIR}/cpp
# ${clipper_library_BINARY_DIR}
# )
add_library(clipperBackend STATIC
${clipper_library_SOURCE_DIR}/clipper.cpp
${clipper_library_SOURCE_DIR}/clipper.hpp)
target_include_directories(clipperBackend INTERFACE ${clipper_library_SOURCE_DIR})
else()
message(FATAL_ERROR "Can't find clipper library and no SVN client found to download.
You can download the clipper sources and define a clipper target in your project, that will work for libnest2d.")
endif()
else()
add_library(clipperBackend INTERFACE)
target_link_libraries(clipperBackend INTERFACE Clipper::Clipper)
endif()
else()
# set(CLIPPER_INCLUDE_DIRS "" PARENT_SCOPE)
# set(CLIPPER_LIBRARIES clipper PARENT_SCOPE)
add_library(clipperBackend INTERFACE)
target_link_libraries(clipperBackend INTERFACE clipper)
endif()
# Clipper backend is not enough on its own, it still needs some functions
# from Boost geometry
if(NOT Boost_FOUND)
find_package(Boost 1.58 REQUIRED)
# TODO automatic download of boost geometry headers
endif()
target_link_libraries(clipperBackend INTERFACE Boost::boost )
#target_sources(ClipperBackend INTERFACE
# ${CMAKE_CURRENT_SOURCE_DIR}/geometries.hpp
# ${CMAKE_CURRENT_SOURCE_DIR}/clipper_polygon.hpp
# ${SRC_DIR}/libnest2d/utils/boost_alg.hpp )
target_compile_definitions(clipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER)
# And finally plug the clipperBackend into libnest2d
# target_link_libraries(libnest2d INTERFACE clipperBackend)

View File

@ -301,7 +301,454 @@ template<class RawShape>
NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
const RawShape& cother)
{
return {};
// Algorithms are from the original algorithm proposed in paper:
// https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf
// /////////////////////////////////////////////////////////////////////////
// Algorithm 1: Obtaining the minkowski sum
// /////////////////////////////////////////////////////////////////////////
// I guess this is not a full minkowski sum of the two input polygons by
// definition. This yields a subset that is compatible with the next 2
// algorithms.
using Result = NfpResult<RawShape>;
using Vertex = TPoint<RawShape>;
using Coord = TCoord<Vertex>;
using Edge = _Segment<Vertex>;
namespace sl = shapelike;
using std::signbit;
using std::sort;
using std::vector;
using std::ref;
using std::reference_wrapper;
// TODO The original algorithms expects the stationary polygon in
// counter clockwise and the orbiter in clockwise order.
// So for preventing any further complication, I will make the input
// the way it should be, than make my way around the orientations.
// Reverse the stationary contour to counter clockwise
auto stcont = sl::contour(cstationary);
{
std::reverse(sl::begin(stcont), sl::end(stcont));
stcont.pop_back();
auto it = std::min_element(sl::begin(stcont), sl::end(stcont),
[](const Vertex& v1, const Vertex& v2) {
return getY(v1) < getY(v2);
});
std::rotate(sl::begin(stcont), it, sl::end(stcont));
sl::addVertex(stcont, sl::front(stcont));
}
RawShape stationary;
sl::contour(stationary) = stcont;
// Reverse the orbiter contour to counter clockwise
auto orbcont = sl::contour(cother);
{
std::reverse(orbcont.begin(), orbcont.end());
// Step 1: Make the orbiter reverse oriented
orbcont.pop_back();
auto it = std::min_element(orbcont.begin(), orbcont.end(),
[](const Vertex& v1, const Vertex& v2) {
return getY(v1) < getY(v2);
});
std::rotate(orbcont.begin(), it, orbcont.end());
orbcont.emplace_back(orbcont.front());
for(auto &v : orbcont) v = -v;
}
// Copy the orbiter (contour only), we will have to work on it
RawShape orbiter;
sl::contour(orbiter) = orbcont;
// An edge with additional data for marking it
struct MarkedEdge {
Edge e; Radians turn_angle = 0; bool is_turning_point = false;
MarkedEdge() = default;
MarkedEdge(const Edge& ed, Radians ta, bool tp):
e(ed), turn_angle(ta), is_turning_point(tp) {}
// debug
std::string label;
};
// Container for marked edges
using EdgeList = vector<MarkedEdge>;
EdgeList A, B;
// This is how an edge list is created from the polygons
auto fillEdgeList = [](EdgeList& L, const RawShape& ppoly, int dir) {
auto& poly = sl::contour(ppoly);
L.reserve(sl::contourVertexCount(poly));
if(dir > 0) {
auto it = poly.begin();
auto nextit = std::next(it);
double turn_angle = 0;
bool is_turn_point = false;
while(nextit != poly.end()) {
L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
it++; nextit++;
}
} else {
auto it = sl::rbegin(poly);
auto nextit = std::next(it);
double turn_angle = 0;
bool is_turn_point = false;
while(nextit != sl::rend(poly)) {
L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
it++; nextit++;
}
}
auto getTurnAngle = [](const Edge& e1, const Edge& e2) {
auto phi = e1.angleToXaxis();
auto phi_prev = e2.angleToXaxis();
auto turn_angle = phi-phi_prev;
if(turn_angle > Pi) turn_angle -= TwoPi;
if(turn_angle < -Pi) turn_angle += TwoPi;
return turn_angle;
};
auto eit = L.begin();
auto enext = std::next(eit);
eit->turn_angle = getTurnAngle(L.front().e, L.back().e);
while(enext != L.end()) {
enext->turn_angle = getTurnAngle( enext->e, eit->e);
eit->is_turning_point =
signbit(enext->turn_angle) != signbit(eit->turn_angle);
++eit; ++enext;
}
L.back().is_turning_point = signbit(L.back().turn_angle) !=
signbit(L.front().turn_angle);
};
// Step 2: Fill the edgelists
fillEdgeList(A, stationary, 1);
fillEdgeList(B, orbiter, 1);
int i = 1;
for(MarkedEdge& me : A) {
std::cout << "a" << i << ":\n\t"
<< getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
<< getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
<< "Turning point: " << (me.is_turning_point ? "yes" : "no")
<< std::endl;
me.label = "a"; me.label += std::to_string(i);
i++;
}
i = 1;
for(MarkedEdge& me : B) {
std::cout << "b" << i << ":\n\t"
<< getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
<< getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
<< "Turning point: " << (me.is_turning_point ? "yes" : "no")
<< std::endl;
me.label = "b"; me.label += std::to_string(i);
i++;
}
// A reference to a marked edge that also knows its container
struct MarkedEdgeRef {
reference_wrapper<MarkedEdge> eref;
reference_wrapper<vector<MarkedEdgeRef>> container;
Coord dir = 1; // Direction modifier
inline Radians angleX() const { return eref.get().e.angleToXaxis(); }
inline const Edge& edge() const { return eref.get().e; }
inline Edge& edge() { return eref.get().e; }
inline bool isTurningPoint() const {
return eref.get().is_turning_point;
}
inline bool isFrom(const vector<MarkedEdgeRef>& cont ) {
return &(container.get()) == &cont;
}
inline bool eq(const MarkedEdgeRef& mr) {
return &(eref.get()) == &(mr.eref.get());
}
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
reference_wrapper<vector<MarkedEdgeRef>> ec):
eref(er), container(ec), dir(1) {}
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
reference_wrapper<vector<MarkedEdgeRef>> ec,
Coord d):
eref(er), container(ec), dir(d) {}
};
using EdgeRefList = vector<MarkedEdgeRef>;
// Comparing two marked edges
auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) {
return e1.angleX() < e2.angleX();
};
EdgeRefList Aref, Bref; // We create containers for the references
Aref.reserve(A.size()); Bref.reserve(B.size());
// Fill reference container for the stationary polygon
std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) {
Aref.emplace_back( ref(me), ref(Aref) );
});
// Fill reference container for the orbiting polygon
std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) {
Bref.emplace_back( ref(me), ref(Bref) );
});
auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure
(const EdgeRefList& Q, const EdgeRefList& R, bool positive)
{
// Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)"
// Sort the containers of edge references and merge them.
// Q could be sorted only once and be reused here but we would still
// need to merge it with sorted(R).
EdgeRefList merged;
EdgeRefList S, seq;
merged.reserve(Q.size() + R.size());
merged.insert(merged.end(), R.begin(), R.end());
std::stable_sort(merged.begin(), merged.end(), sortfn);
merged.insert(merged.end(), Q.begin(), Q.end());
std::stable_sort(merged.begin(), merged.end(), sortfn);
// Step 2 "set i = 1, k = 1, direction = 1, s1 = q1"
// we don't use i, instead, q is an iterator into Q. k would be an index
// into the merged sequence but we use "it" as an iterator for that
// here we obtain references for the containers for later comparisons
const auto& Rcont = R.begin()->container.get();
const auto& Qcont = Q.begin()->container.get();
// Set the initial direction
Coord dir = 1;
// roughly i = 1 (so q = Q.begin()) and s1 = q1 so S[0] = q;
if(positive) {
auto q = Q.begin();
S.emplace_back(*q);
// Roughly step 3
std::cout << "merged size: " << merged.size() << std::endl;
auto mit = merged.begin();
for(bool finish = false; !finish && q != Q.end();) {
++q; // "Set i = i + 1"
while(!finish && mit != merged.end()) {
if(mit->isFrom(Rcont)) {
auto s = *mit;
s.dir = dir;
S.emplace_back(s);
}
if(mit->eq(*q)) {
S.emplace_back(*q);
if(mit->isTurningPoint()) dir = -dir;
if(q == Q.begin()) finish = true;
break;
}
mit += dir;
// __nfp::advance(mit, merged, dir > 0);
}
}
} else {
auto q = Q.rbegin();
S.emplace_back(*q);
// Roughly step 3
std::cout << "merged size: " << merged.size() << std::endl;
auto mit = merged.begin();
for(bool finish = false; !finish && q != Q.rend();) {
++q; // "Set i = i + 1"
while(!finish && mit != merged.end()) {
if(mit->isFrom(Rcont)) {
auto s = *mit;
s.dir = dir;
S.emplace_back(s);
}
if(mit->eq(*q)) {
S.emplace_back(*q);
S.back().dir = -1;
if(mit->isTurningPoint()) dir = -dir;
if(q == Q.rbegin()) finish = true;
break;
}
mit += dir;
// __nfp::advance(mit, merged, dir > 0);
}
}
}
// Step 4:
// "Let starting edge r1 be in position si in sequence"
// whaaat? I guess this means the following:
auto it = S.begin();
while(!it->eq(*R.begin())) ++it;
// "Set j = 1, next = 2, direction = 1, seq1 = si"
// we don't use j, seq is expanded dynamically.
dir = 1;
auto next = std::next(R.begin()); seq.emplace_back(*it);
// Step 5:
// "If all si edges have been allocated to seqj" should mean that
// we loop until seq has equal size with S
auto send = it; //it == S.begin() ? it : std::prev(it);
while(it != S.end()) {
++it; if(it == S.end()) it = S.begin();
if(it == send) break;
if(it->isFrom(Qcont)) {
seq.emplace_back(*it); // "If si is from Q, j = j + 1, seqj = si"
// "If si is a turning point in Q,
// direction = - direction, next = next + direction"
if(it->isTurningPoint()) {
dir = -dir;
next += dir;
// __nfp::advance(next, R, dir > 0);
}
}
if(it->eq(*next) /*&& dir == next->dir*/) { // "If si = direction.rnext"
// "j = j + 1, seqj = si, next = next + direction"
seq.emplace_back(*it);
next += dir;
// __nfp::advance(next, R, dir > 0);
}
}
return seq;
};
std::vector<EdgeRefList> seqlist;
seqlist.reserve(Bref.size());
EdgeRefList Bslope = Bref; // copy Bref, we will make a slope diagram
// make the slope diagram of B
std::sort(Bslope.begin(), Bslope.end(), sortfn);
auto slopeit = Bslope.begin(); // search for the first turning point
while(!slopeit->isTurningPoint() && slopeit != Bslope.end()) slopeit++;
if(slopeit == Bslope.end()) {
// no turning point means convex polygon.
seqlist.emplace_back(mink(Aref, Bref, true));
} else {
int dir = 1;
auto firstturn = Bref.begin();
while(!firstturn->eq(*slopeit)) ++firstturn;
assert(firstturn != Bref.end());
EdgeRefList bgroup; bgroup.reserve(Bref.size());
bgroup.emplace_back(*slopeit);
auto b_it = std::next(firstturn);
while(b_it != firstturn) {
if(b_it == Bref.end()) b_it = Bref.begin();
while(!slopeit->eq(*b_it)) {
__nfp::advance(slopeit, Bslope, dir > 0);
}
if(!slopeit->isTurningPoint()) {
bgroup.emplace_back(*slopeit);
} else {
if(!bgroup.empty()) {
if(dir > 0) bgroup.emplace_back(*slopeit);
for(auto& me : bgroup) {
std::cout << me.eref.get().label << ", ";
}
std::cout << std::endl;
seqlist.emplace_back(mink(Aref, bgroup, dir == 1 ? true : false));
bgroup.clear();
if(dir < 0) bgroup.emplace_back(*slopeit);
} else {
bgroup.emplace_back(*slopeit);
}
dir *= -1;
}
++b_it;
}
}
// while(it != Bref.end()) // This is step 3 and step 4 in one loop
// if(it->isTurningPoint()) {
// R = {R.last, it++};
// auto seq = mink(Q, R, orientation);
// // TODO step 6 (should be 5 shouldn't it?): linking edges from A
// // I don't get this step
// seqlist.insert(seqlist.end(), seq.begin(), seq.end());
// orientation = !orientation;
// } else ++it;
// if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true);
// /////////////////////////////////////////////////////////////////////////
// Algorithm 2: breaking Minkowski sums into track line trips
// /////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Algorithm 3: finding the boundary of the NFP from track line trips
// /////////////////////////////////////////////////////////////////////////
for(auto& seq : seqlist) {
std::cout << "seqlist size: " << seq.size() << std::endl;
for(auto& s : seq) {
std::cout << (s.dir > 0 ? "" : "-") << s.eref.get().label << ", ";
}
std::cout << std::endl;
}
auto& seq = seqlist.front();
RawShape rsh;
Vertex top_nfp;
std::vector<Edge> edgelist; edgelist.reserve(seq.size());
for(auto& s : seq) {
edgelist.emplace_back(s.eref.get().e);
}
__nfp::buildPolygon(edgelist, rsh, top_nfp);
return Result(rsh, top_nfp);
}
// Specializable NFP implementation class. Specialize it if you have a faster

View File

@ -1,869 +1,134 @@
#ifndef LIBNEST2D_HPP
#define LIBNEST2D_HPP
#include <memory>
#include <vector>
#include <map>
#include <array>
#include <algorithm>
#include <functional>
// The type of backend should be set conditionally by the cmake configuriation
// for now we set it statically to clipper backend
#ifdef LIBNEST2D_GEOMETRIES_clipper
#include <libnest2d/backends/clipper/geometries.hpp>
#endif
#include <libnest2d/geometry_traits.hpp>
#ifdef LIBNEST2D_OPTIMIZER_nlopt
// We include the stock optimizers for local and global optimization
#include <libnest2d/optimizers/nlopt/subplex.hpp> // Local subplex for NfpPlacer
#include <libnest2d/optimizers/nlopt/genetic.hpp> // Genetic for min. bounding box
#endif
#include <libnest2d/nester.hpp>
#include <libnest2d/placers/bottomleftplacer.hpp>
#include <libnest2d/placers/nfpplacer.hpp>
#include <libnest2d/selections/firstfit.hpp>
#include <libnest2d/selections/filler.hpp>
#include <libnest2d/selections/djd_heuristic.hpp>
namespace libnest2d {
static const constexpr int BIN_ID_UNSET = -1;
/**
* \brief An item to be placed on a bin.
*
* It holds a copy of the original shape object but supports move construction
* from the shape objects if its an rvalue reference. This way we can construct
* the items without the cost of copying a potentially large amount of input.
*
* The results of some calculations are cached for maintaining fast run times.
* For this reason, memory demands are much higher but this should pay off.
*/
template<class RawShape>
class _Item {
using Coord = TCoord<TPoint<RawShape>>;
using Vertex = TPoint<RawShape>;
using Box = _Box<Vertex>;
using VertexConstIterator = typename TContour<RawShape>::const_iterator;
// The original shape that gets encapsulated.
RawShape sh_;
// Transformation data
Vertex translation_{0, 0};
Radians rotation_{0.0};
Coord inflation_{0};
// Info about whether the transformations will have to take place
// This is needed because if floating point is used, it is hard to say
// that a zero angle is not a rotation because of testing for equality.
bool has_rotation_ = false, has_translation_ = false, has_inflation_ = false;
// For caching the calculations as they can get pretty expensive.
mutable RawShape tr_cache_;
mutable bool tr_cache_valid_ = false;
mutable double area_cache_ = 0;
mutable bool area_cache_valid_ = false;
mutable RawShape inflate_cache_;
mutable bool inflate_cache_valid_ = false;
enum class Convexity: char {
UNCHECKED,
C_TRUE,
C_FALSE
};
mutable Convexity convexity_ = Convexity::UNCHECKED;
mutable VertexConstIterator rmt_; // rightmost top vertex
mutable VertexConstIterator lmb_; // leftmost bottom vertex
mutable bool rmt_valid_ = false, lmb_valid_ = false;
mutable struct BBCache {
Box bb; bool valid;
BBCache(): valid(false) {}
} bb_cache_;
int binid_{BIN_ID_UNSET}, priority_{0};
bool fixed_{false};
public:
/// The type of the shape which was handed over as the template argument.
using ShapeType = RawShape;
/**
* \brief Iterator type for the outer vertices.
*
* Only const iterators can be used. The _Item type is not intended to
* modify the carried shapes from the outside. The main purpose of this type
* is to cache the calculation results from the various operators it
* supports. Giving out a non const iterator would make it impossible to
* perform correct cache invalidation.
*/
using Iterator = VertexConstIterator;
/**
* @brief Get the orientation of the polygon.
*
* The orientation have to be specified as a specialization of the
* OrientationType struct which has a Value constant.
*
* @return The orientation type identifier for the _Item type.
*/
static BP2D_CONSTEXPR Orientation orientation() {
return OrientationType<RawShape>::Value;
}
/**
* @brief Constructing an _Item form an existing raw shape. The shape will
* be copied into the _Item object.
* @param sh The original shape object.
*/
explicit inline _Item(const RawShape& sh): sh_(sh) {}
/**
* @brief Construction of an item by moving the content of the raw shape,
* assuming that it supports move semantics.
* @param sh The original shape object.
*/
explicit inline _Item(RawShape&& sh): sh_(std::move(sh)) {}
/**
* @brief Create an item from an initializer list.
* @param il The initializer list of vertices.
*/
inline _Item(const std::initializer_list< Vertex >& il):
sh_(sl::create<RawShape>(il)) {}
inline _Item(const TContour<RawShape>& contour,
const THolesContainer<RawShape>& holes = {}):
sh_(sl::create<RawShape>(contour, holes)) {}
inline _Item(TContour<RawShape>&& contour,
THolesContainer<RawShape>&& holes):
sh_(sl::create<RawShape>(std::move(contour), std::move(holes))) {}
inline bool isFixed() const noexcept { return fixed_; }
inline void markAsFixedInBin(int binid)
{
fixed_ = binid >= 0;
binid_ = binid;
}
inline void binId(int idx) { binid_ = idx; }
inline int binId() const noexcept { return binid_; }
inline void priority(int p) { priority_ = p; }
inline int priority() const noexcept { return priority_; }
/**
* @brief Convert the polygon to string representation. The format depends
* on the implementation of the polygon.
* @return
*/
inline std::string toString() const
{
return sl::toString(sh_);
}
/// Iterator tho the first contour vertex in the polygon.
inline Iterator begin() const
{
return sl::cbegin(sh_);
}
/// Alias to begin()
inline Iterator cbegin() const
{
return sl::cbegin(sh_);
}
/// Iterator to the last contour vertex.
inline Iterator end() const
{
return sl::cend(sh_);
}
/// Alias to end()
inline Iterator cend() const
{
return sl::cend(sh_);
}
/**
* @brief Get a copy of an outer vertex within the carried shape.
*
* Note that the vertex considered here is taken from the original shape
* that this item is constructed from. This means that no transformation is
* applied to the shape in this call.
*
* @param idx The index of the requested vertex.
* @return A copy of the requested vertex.
*/
inline Vertex vertex(unsigned long idx) const
{
return sl::vertex(sh_, idx);
}
/**
* @brief Modify a vertex.
*
* Note that this method will invalidate every cached calculation result
* including polygon offset and transformations.
*
* @param idx The index of the requested vertex.
* @param v The new vertex data.
*/
inline void setVertex(unsigned long idx, const Vertex& v )
{
invalidateCache();
sl::vertex(sh_, idx) = v;
}
/**
* @brief Calculate the shape area.
*
* The method returns absolute value and does not reflect polygon
* orientation. The result is cached, subsequent calls will have very little
* cost.
* @return The shape area in floating point double precision.
*/
inline double area() const {
double ret ;
if(area_cache_valid_) ret = area_cache_;
else {
ret = sl::area(infaltedShape());
area_cache_ = ret;
area_cache_valid_ = true;
}
return ret;
}
inline bool isContourConvex() const {
bool ret = false;
switch(convexity_) {
case Convexity::UNCHECKED:
ret = sl::isConvex(sl::contour(transformedShape()));
convexity_ = ret? Convexity::C_TRUE : Convexity::C_FALSE;
break;
case Convexity::C_TRUE: ret = true; break;
case Convexity::C_FALSE:;
}
return ret;
}
inline bool isHoleConvex(unsigned /*holeidx*/) const {
return false;
}
inline bool areHolesConvex() const {
return false;
}
/// The number of the outer ring vertices.
inline size_t vertexCount() const {
return sl::contourVertexCount(sh_);
}
inline size_t holeCount() const {
return sl::holeCount(sh_);
}
/**
* @brief isPointInside
* @param p
* @return
*/
inline bool isInside(const Vertex& p) const
{
return sl::isInside(p, transformedShape());
}
inline bool isInside(const _Item& sh) const
{
return sl::isInside(transformedShape(), sh.transformedShape());
}
inline bool isInside(const RawShape& sh) const
{
return sl::isInside(transformedShape(), sh);
}
inline bool isInside(const _Box<TPoint<RawShape>>& box) const;
inline bool isInside(const _Circle<TPoint<RawShape>>& box) const;
inline void translate(const Vertex& d) BP2D_NOEXCEPT
{
translation(translation() + d);
}
inline void rotate(const Radians& rads) BP2D_NOEXCEPT
{
rotation(rotation() + rads);
}
inline void inflation(Coord distance) BP2D_NOEXCEPT
{
inflation_ = distance;
has_inflation_ = true;
invalidateCache();
}
inline Coord inflation() const BP2D_NOEXCEPT {
return inflation_;
}
inline void inflate(Coord distance) BP2D_NOEXCEPT
{
inflation(inflation() + distance);
}
inline Radians rotation() const BP2D_NOEXCEPT
{
return rotation_;
}
inline TPoint<RawShape> translation() const BP2D_NOEXCEPT
{
return translation_;
}
inline void rotation(Radians rot) BP2D_NOEXCEPT
{
if(rotation_ != rot) {
rotation_ = rot; has_rotation_ = true; tr_cache_valid_ = false;
rmt_valid_ = false; lmb_valid_ = false;
bb_cache_.valid = false;
}
}
inline void translation(const TPoint<RawShape>& tr) BP2D_NOEXCEPT
{
if(translation_ != tr) {
translation_ = tr; has_translation_ = true; tr_cache_valid_ = false;
//bb_cache_.valid = false;
}
}
inline const RawShape& transformedShape() const
{
if(tr_cache_valid_) return tr_cache_;
RawShape cpy = infaltedShape();
if(has_rotation_) sl::rotate(cpy, rotation_);
if(has_translation_) sl::translate(cpy, translation_);
tr_cache_ = cpy; tr_cache_valid_ = true;
rmt_valid_ = false; lmb_valid_ = false;
return tr_cache_;
}
inline operator RawShape() const
{
return transformedShape();
}
inline const RawShape& rawShape() const BP2D_NOEXCEPT
{
return sh_;
}
inline void resetTransformation() BP2D_NOEXCEPT
{
has_translation_ = false; has_rotation_ = false; has_inflation_ = false;
invalidateCache();
}
inline Box boundingBox() const {
if(!bb_cache_.valid) {
if(!has_rotation_)
bb_cache_.bb = sl::boundingBox(infaltedShape());
else {
// TODO make sure this works
auto rotsh = infaltedShape();
sl::rotate(rotsh, rotation_);
bb_cache_.bb = sl::boundingBox(rotsh);
}
bb_cache_.valid = true;
}
auto &bb = bb_cache_.bb; auto &tr = translation_;
return {bb.minCorner() + tr, bb.maxCorner() + tr };
}
inline Vertex referenceVertex() const {
return rightmostTopVertex();
}
inline Vertex rightmostTopVertex() const {
if(!rmt_valid_ || !tr_cache_valid_) { // find max x and max y vertex
auto& tsh = transformedShape();
rmt_ = std::max_element(sl::cbegin(tsh), sl::cend(tsh), vsort);
rmt_valid_ = true;
}
return *rmt_;
}
inline Vertex leftmostBottomVertex() const {
if(!lmb_valid_ || !tr_cache_valid_) { // find min x and min y vertex
auto& tsh = transformedShape();
lmb_ = std::min_element(sl::cbegin(tsh), sl::cend(tsh), vsort);
lmb_valid_ = true;
}
return *lmb_;
}
//Static methods:
inline static bool intersects(const _Item& sh1, const _Item& sh2)
{
return sl::intersects(sh1.transformedShape(),
sh2.transformedShape());
}
inline static bool touches(const _Item& sh1, const _Item& sh2)
{
return sl::touches(sh1.transformedShape(),
sh2.transformedShape());
}
private:
inline const RawShape& infaltedShape() const {
if(has_inflation_ ) {
if(inflate_cache_valid_) return inflate_cache_;
inflate_cache_ = sh_;
sl::offset(inflate_cache_, inflation_);
inflate_cache_valid_ = true;
return inflate_cache_;
}
return sh_;
}
inline void invalidateCache() const BP2D_NOEXCEPT
{
tr_cache_valid_ = false;
lmb_valid_ = false; rmt_valid_ = false;
area_cache_valid_ = false;
inflate_cache_valid_ = false;
bb_cache_.valid = false;
convexity_ = Convexity::UNCHECKED;
}
static inline bool vsort(const Vertex& v1, const Vertex& v2)
{
TCompute<Vertex> x1 = getX(v1), x2 = getX(v2);
TCompute<Vertex> y1 = getY(v1), y2 = getY(v2);
return y1 == y2 ? x1 < x2 : y1 < y2;
}
using Point = PointImpl;
using Coord = TCoord<PointImpl>;
using Box = _Box<PointImpl>;
using Segment = _Segment<PointImpl>;
using Circle = _Circle<PointImpl>;
using Item = _Item<PolygonImpl>;
using Rectangle = _Rectangle<PolygonImpl>;
using PackGroup = _PackGroup<PolygonImpl>;
using FillerSelection = selections::_FillerSelection<PolygonImpl>;
using FirstFitSelection = selections::_FirstFitSelection<PolygonImpl>;
using DJDHeuristic = selections::_DJDHeuristic<PolygonImpl>;
template<class Bin> // Generic placer for arbitrary bin types
using _NfpPlacer = placers::_NofitPolyPlacer<PolygonImpl, Bin>;
// NfpPlacer is with Box bin
using NfpPlacer = _NfpPlacer<Box>;
// This supports only box shaped bins
using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>;
#ifdef LIBNEST2D_STATIC
extern template class _Nester<NfpPlacer, FirstFitSelection>;
extern template class _Nester<BottomLeftPlacer, FirstFitSelection>;
extern template std::size_t _Nester<NfpPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
extern template std::size_t _Nester<BottomLeftPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
#endif
template<class Placer = NfpPlacer, class Selector = FirstFitSelection>
struct NestConfig {
typename Placer::Config placer_config;
typename Selector::Config selector_config;
using Placement = typename Placer::Config;
using Selection = typename Selector::Config;
NestConfig() = default;
NestConfig(const typename Placer::Config &cfg) : placer_config{cfg} {}
NestConfig(const typename Selector::Config &cfg) : selector_config{cfg} {}
NestConfig(const typename Placer::Config & pcfg,
const typename Selector::Config &scfg)
: placer_config{pcfg}, selector_config{scfg} {}
};
/**
* \brief Subclass of _Item for regular rectangle items.
*/
template<class RawShape>
class _Rectangle: public _Item<RawShape> {
using _Item<RawShape>::vertex;
using TO = Orientation;
public:
struct NestControl {
ProgressFunction progressfn;
StopCondition stopcond = []{ return false; };
using Unit = TCoord<TPoint<RawShape>>;
template<TO o = OrientationType<RawShape>::Value>
inline _Rectangle(Unit width, Unit height,
// disable this ctor if o != CLOCKWISE
enable_if_t< o == TO::CLOCKWISE, int> = 0 ):
_Item<RawShape>( sl::create<RawShape>( {
{0, 0},
{0, height},
{width, height},
{width, 0},
{0, 0}
} ))
{
}
template<TO o = OrientationType<RawShape>::Value>
inline _Rectangle(Unit width, Unit height,
// disable this ctor if o != COUNTER_CLOCKWISE
enable_if_t< o == TO::COUNTER_CLOCKWISE, int> = 0 ):
_Item<RawShape>( sl::create<RawShape>( {
{0, 0},
{width, 0},
{width, height},
{0, height},
{0, 0}
} ))
{
}
inline Unit width() const BP2D_NOEXCEPT {
return getX(vertex(2));
}
inline Unit height() const BP2D_NOEXCEPT {
return getY(vertex(2));
}
NestControl() = default;
NestControl(ProgressFunction pr) : progressfn{std::move(pr)} {}
NestControl(StopCondition sc) : stopcond{std::move(sc)} {}
NestControl(ProgressFunction pr, StopCondition sc)
: progressfn{std::move(pr)}, stopcond{std::move(sc)}
{}
};
template<class RawShape>
inline bool _Item<RawShape>::isInside(const _Box<TPoint<RawShape>>& box) const {
return sl::isInside(boundingBox(), box);
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Iterator = std::vector<Item>::iterator>
std::size_t nest(Iterator from, Iterator to,
const typename Placer::BinType & bin,
Coord dist = 0,
const NestConfig<Placer, Selector> &cfg = {},
NestControl ctl = {})
{
_Nester<Placer, Selector> nester{bin, dist, cfg.placer_config, cfg.selector_config};
if(ctl.progressfn) nester.progressIndicator(ctl.progressfn);
if(ctl.stopcond) nester.stopCondition(ctl.stopcond);
return nester.execute(from, to);
}
template<class RawShape> inline bool
_Item<RawShape>::isInside(const _Circle<TPoint<RawShape>>& circ) const {
return sl::isInside(transformedShape(), circ);
#ifdef LIBNEST2D_STATIC
extern template class _Nester<NfpPlacer, FirstFitSelection>;
extern template class _Nester<BottomLeftPlacer, FirstFitSelection>;
extern template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box & bin,
Coord dist,
const NestConfig<NfpPlacer, FirstFitSelection> &cfg,
NestControl ctl);
extern template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box & bin,
Coord dist,
const NestConfig<BottomLeftPlacer, FirstFitSelection> &cfg,
NestControl ctl);
#endif
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Container = std::vector<Item>>
std::size_t nest(Container&& cont,
const typename Placer::BinType & bin,
Coord dist = 0,
const NestConfig<Placer, Selector> &cfg = {},
NestControl ctl = {})
{
return nest<Placer, Selector>(cont.begin(), cont.end(), bin, dist, cfg, ctl);
}
template<class RawShape> using _ItemRef = std::reference_wrapper<_Item<RawShape>>;
template<class RawShape> using _ItemGroup = std::vector<_ItemRef<RawShape>>;
/**
* \brief A list of packed item vectors. Each vector represents a bin.
*/
template<class RawShape>
using _PackGroup = std::vector<std::vector<_ItemRef<RawShape>>>;
template<class Iterator>
struct ConstItemRange {
Iterator from;
Iterator to;
bool valid = false;
ConstItemRange() = default;
ConstItemRange(Iterator f, Iterator t): from(f), to(t), valid(true) {}
};
template<class Container>
inline ConstItemRange<typename Container::const_iterator>
rem(typename Container::const_iterator it, const Container& cont) {
return {std::next(it), cont.end()};
}
/**
* \brief A wrapper interface (trait) class for any placement strategy provider.
*
* If a client wants to use its own placement algorithm, all it has to do is to
* specialize this class template and define all the ten methods it has. It can
* use the strategies::PlacerBoilerplace class for creating a new placement
* strategy where only the constructor and the trypack method has to be provided
* and it will work out of the box.
*/
template<class PlacementStrategy>
class PlacementStrategyLike {
PlacementStrategy impl_;
public:
using RawShape = typename PlacementStrategy::ShapeType;
/// The item type that the placer works with.
using Item = _Item<RawShape>;
/// The placer's config type. Should be a simple struct but can be anything.
using Config = typename PlacementStrategy::Config;
/**
* \brief The type of the bin that the placer works with.
*
* Can be a box or an arbitrary shape or just a width or height without a
* second dimension if an infinite bin is considered.
*/
using BinType = typename PlacementStrategy::BinType;
/**
* \brief Pack result that can be used to accept or discard it. See trypack
* method.
*/
using PackResult = typename PlacementStrategy::PackResult;
using ItemGroup = _ItemGroup<RawShape>;
using DefaultIterator = typename ItemGroup::const_iterator;
/**
* @brief Constructor taking the bin and an optional configuration.
* @param bin The bin object whose type is defined by the placement strategy.
* @param config The configuration for the particular placer.
*/
explicit PlacementStrategyLike(const BinType& bin,
const Config& config = Config()):
impl_(bin)
{
configure(config);
}
/**
* @brief Provide a different configuration for the placer.
*
* Note that it depends on the particular placer implementation how it
* reacts to config changes in the middle of a calculation.
*
* @param config The configuration object defined by the placement strategy.
*/
inline void configure(const Config& config) { impl_.configure(config); }
/**
* Try to pack an item with a result object that contains the packing
* information for later accepting it.
*
* \param item_store A container of items that are intended to be packed
* later. Can be used by the placer to switch tactics. When it's knows that
* many items will come a greedy strategy may not be the best.
* \param from The iterator to the item from which the packing should start,
* including the pointed item
* \param count How many items should be packed. If the value is 1, than
* just the item pointed to by "from" argument should be packed.
*/
template<class Iter = DefaultIterator>
inline PackResult trypack(
Item& item,
const ConstItemRange<Iter>& remaining = ConstItemRange<Iter>())
{
return impl_.trypack(item, remaining);
}
/**
* @brief A method to accept a previously tried item (or items).
*
* If the pack result is a failure the method should ignore it.
* @param r The result of a previous trypack call.
*/
inline void accept(PackResult& r) { impl_.accept(r); }
/**
* @brief pack Try to pack and immediately accept it on success.
*
* A default implementation would be to call
* { auto&& r = trypack(...); accept(r); return r; } but we should let the
* implementor of the placement strategy to harvest any optimizations from
* the absence of an intermediate step. The above version can still be used
* in the implementation.
*
* @param item The item to pack.
* @return Returns true if the item was packed or false if it could not be
* packed.
*/
template<class Range = ConstItemRange<DefaultIterator>>
inline bool pack(
Item& item,
const Range& remaining = Range())
{
return impl_.pack(item, remaining);
}
/**
* This method makes possible to "preload" some items into the placer. It
* will not move these items but will consider them as already packed.
*/
inline void preload(const ItemGroup& packeditems)
{
impl_.preload(packeditems);
}
/// Unpack the last element (remove it from the list of packed items).
inline void unpackLast() { impl_.unpackLast(); }
/// Get the bin object.
inline const BinType& bin() const { return impl_.bin(); }
/// Set a new bin object.
inline void bin(const BinType& bin) { impl_.bin(bin); }
/// Get the packed items.
inline ItemGroup getItems() { return impl_.getItems(); }
/// Clear the packed items so a new session can be started.
inline void clearItems() { impl_.clearItems(); }
inline double filledArea() const { return impl_.filledArea(); }
};
// The progress function will be called with the number of placed items
using ProgressFunction = std::function<void(unsigned)>;
using StopCondition = std::function<bool(void)>;
/**
* A wrapper interface (trait) class for any selections strategy provider.
*/
template<class SelectionStrategy>
class SelectionStrategyLike {
SelectionStrategy impl_;
public:
using RawShape = typename SelectionStrategy::ShapeType;
using Item = _Item<RawShape>;
using PackGroup = _PackGroup<RawShape>;
using Config = typename SelectionStrategy::Config;
/**
* @brief Provide a different configuration for the selection strategy.
*
* Note that it depends on the particular placer implementation how it
* reacts to config changes in the middle of a calculation.
*
* @param config The configuration object defined by the selection strategy.
*/
inline void configure(const Config& config) {
impl_.configure(config);
}
/**
* @brief A function callback which should be called whenever an item or
* a group of items where successfully packed.
* @param fn A function callback object taking one unsigned integer as the
* number of the remaining items to pack.
*/
void progressIndicator(ProgressFunction fn) { impl_.progressIndicator(fn); }
void stopCondition(StopCondition cond) { impl_.stopCondition(cond); }
/**
* \brief A method to start the calculation on the input sequence.
*
* \tparam TPlacer The only mandatory template parameter is the type of
* placer compatible with the PlacementStrategyLike interface.
*
* \param first, last The first and last iterator if the input sequence. It
* can be only an iterator of a type convertible to Item.
* \param bin. The shape of the bin. It has to be supported by the placement
* strategy.
* \param An optional config object for the placer.
*/
template<class TPlacer, class TIterator,
class TBin = typename PlacementStrategyLike<TPlacer>::BinType,
class PConfig = typename PlacementStrategyLike<TPlacer>::Config>
inline void packItems(
TIterator first,
TIterator last,
TBin&& bin,
PConfig&& config = PConfig() )
{
impl_.template packItems<TPlacer>(first, last,
std::forward<TBin>(bin),
std::forward<PConfig>(config));
}
/**
* @brief Get the items for a particular bin.
* @param binIndex The index of the requested bin.
* @return Returns a list of all items packed into the requested bin.
*/
inline const PackGroup& getResult() const {
return impl_.getResult();
}
void clear() { impl_.clear(); }
};
/**
* The _Nester is the front-end class for the libnest2d library. It takes the
* input items and changes their transformations to be inside the provided bin.
*/
template<class PlacementStrategy, class SelectionStrategy >
class _Nester {
using TSel = SelectionStrategyLike<SelectionStrategy>;
TSel selector_;
public:
using Item = typename PlacementStrategy::Item;
using ShapeType = typename Item::ShapeType;
using ItemRef = std::reference_wrapper<Item>;
using TPlacer = PlacementStrategyLike<PlacementStrategy>;
using BinType = typename TPlacer::BinType;
using PlacementConfig = typename TPlacer::Config;
using SelectionConfig = typename TSel::Config;
using Coord = TCoord<TPoint<typename Item::ShapeType>>;
using PackGroup = _PackGroup<typename Item::ShapeType>;
using ResultType = PackGroup;
private:
BinType bin_;
PlacementConfig pconfig_;
Coord min_obj_distance_;
using SItem = typename SelectionStrategy::Item;
using TPItem = remove_cvref_t<Item>;
using TSItem = remove_cvref_t<SItem>;
StopCondition stopfn_;
template<class It> using TVal = remove_ref_t<typename It::value_type>;
template<class It, class Out>
using ItemIteratorOnly =
enable_if_t<std::is_convertible<TVal<It>&, TPItem&>::value, Out>;
public:
/**
* \brief Constructor taking the bin as the only mandatory parameter.
*
* \param bin The bin shape that will be used by the placers. The type
* of the bin should be one that is supported by the placer type.
*/
template<class TBinType = BinType,
class PConf = PlacementConfig,
class SConf = SelectionConfig>
_Nester(TBinType&& bin, Coord min_obj_distance = 0,
const PConf& pconfig = PConf(), const SConf& sconfig = SConf()):
bin_(std::forward<TBinType>(bin)),
pconfig_(pconfig),
min_obj_distance_(min_obj_distance)
{
static_assert( std::is_same<TPItem, TSItem>::value,
"Incompatible placement and selection strategy!");
selector_.configure(sconfig);
}
void configure(const PlacementConfig& pconf) { pconfig_ = pconf; }
void configure(const SelectionConfig& sconf) { selector_.configure(sconf); }
void configure(const PlacementConfig& pconf, const SelectionConfig& sconf)
{
pconfig_ = pconf;
selector_.configure(sconf);
}
void configure(const SelectionConfig& sconf, const PlacementConfig& pconf)
{
pconfig_ = pconf;
selector_.configure(sconf);
}
/**
* \brief Arrange an input sequence of _Item-s.
*
* To get the result, call the translation(), rotation() and binId()
* methods of each item. If only the transformed polygon is needed, call
* transformedShape() to get the properly transformed shapes.
*
* The number of groups in the pack group is the number of bins opened by
* the selection algorithm.
*/
template<class It>
inline ItemIteratorOnly<It, size_t> execute(It from, It to)
{
auto infl = static_cast<Coord>(std::ceil(min_obj_distance_/2.0));
if(infl > 0) std::for_each(from, to, [this, infl](Item& item) {
item.inflate(infl);
});
selector_.template packItems<PlacementStrategy>(
from, to, bin_, pconfig_);
if(min_obj_distance_ > 0) std::for_each(from, to, [infl](Item& item) {
item.inflate(-infl);
});
return selector_.getResult().size();
}
/// Set a progress indicator function object for the selector.
inline _Nester& progressIndicator(ProgressFunction func)
{
selector_.progressIndicator(func); return *this;
}
/// Set a predicate to tell when to abort nesting.
inline _Nester& stopCondition(StopCondition fn)
{
stopfn_ = fn; selector_.stopCondition(fn); return *this;
}
inline const PackGroup& lastResult() const
{
return selector_.getResult();
}
};
}
#endif // LIBNEST2D_HPP

View File

@ -0,0 +1,869 @@
#ifndef NESTER_HPP
#define NESTER_HPP
#include <memory>
#include <vector>
#include <map>
#include <array>
#include <algorithm>
#include <functional>
#include <libnest2d/geometry_traits.hpp>
namespace libnest2d {
static const constexpr int BIN_ID_UNSET = -1;
/**
* \brief An item to be placed on a bin.
*
* It holds a copy of the original shape object but supports move construction
* from the shape objects if its an rvalue reference. This way we can construct
* the items without the cost of copying a potentially large amount of input.
*
* The results of some calculations are cached for maintaining fast run times.
* For this reason, memory demands are much higher but this should pay off.
*/
template<class RawShape>
class _Item {
using Coord = TCoord<TPoint<RawShape>>;
using Vertex = TPoint<RawShape>;
using Box = _Box<Vertex>;
using VertexConstIterator = typename TContour<RawShape>::const_iterator;
// The original shape that gets encapsulated.
RawShape sh_;
// Transformation data
Vertex translation_{0, 0};
Radians rotation_{0.0};
Coord inflation_{0};
// Info about whether the transformations will have to take place
// This is needed because if floating point is used, it is hard to say
// that a zero angle is not a rotation because of testing for equality.
bool has_rotation_ = false, has_translation_ = false, has_inflation_ = false;
// For caching the calculations as they can get pretty expensive.
mutable RawShape tr_cache_;
mutable bool tr_cache_valid_ = false;
mutable double area_cache_ = 0;
mutable bool area_cache_valid_ = false;
mutable RawShape inflate_cache_;
mutable bool inflate_cache_valid_ = false;
enum class Convexity: char {
UNCHECKED,
C_TRUE,
C_FALSE
};
mutable Convexity convexity_ = Convexity::UNCHECKED;
mutable VertexConstIterator rmt_; // rightmost top vertex
mutable VertexConstIterator lmb_; // leftmost bottom vertex
mutable bool rmt_valid_ = false, lmb_valid_ = false;
mutable struct BBCache {
Box bb; bool valid;
BBCache(): valid(false) {}
} bb_cache_;
int binid_{BIN_ID_UNSET}, priority_{0};
bool fixed_{false};
public:
/// The type of the shape which was handed over as the template argument.
using ShapeType = RawShape;
/**
* \brief Iterator type for the outer vertices.
*
* Only const iterators can be used. The _Item type is not intended to
* modify the carried shapes from the outside. The main purpose of this type
* is to cache the calculation results from the various operators it
* supports. Giving out a non const iterator would make it impossible to
* perform correct cache invalidation.
*/
using Iterator = VertexConstIterator;
/**
* @brief Get the orientation of the polygon.
*
* The orientation have to be specified as a specialization of the
* OrientationType struct which has a Value constant.
*
* @return The orientation type identifier for the _Item type.
*/
static BP2D_CONSTEXPR Orientation orientation() {
return OrientationType<RawShape>::Value;
}
/**
* @brief Constructing an _Item form an existing raw shape. The shape will
* be copied into the _Item object.
* @param sh The original shape object.
*/
explicit inline _Item(const RawShape& sh): sh_(sh) {}
/**
* @brief Construction of an item by moving the content of the raw shape,
* assuming that it supports move semantics.
* @param sh The original shape object.
*/
explicit inline _Item(RawShape&& sh): sh_(std::move(sh)) {}
/**
* @brief Create an item from an initializer list.
* @param il The initializer list of vertices.
*/
inline _Item(const std::initializer_list< Vertex >& il):
sh_(sl::create<RawShape>(il)) {}
inline _Item(const TContour<RawShape>& contour,
const THolesContainer<RawShape>& holes = {}):
sh_(sl::create<RawShape>(contour, holes)) {}
inline _Item(TContour<RawShape>&& contour,
THolesContainer<RawShape>&& holes):
sh_(sl::create<RawShape>(std::move(contour), std::move(holes))) {}
inline bool isFixed() const noexcept { return fixed_; }
inline void markAsFixedInBin(int binid)
{
fixed_ = binid >= 0;
binid_ = binid;
}
inline void binId(int idx) { binid_ = idx; }
inline int binId() const noexcept { return binid_; }
inline void priority(int p) { priority_ = p; }
inline int priority() const noexcept { return priority_; }
/**
* @brief Convert the polygon to string representation. The format depends
* on the implementation of the polygon.
* @return
*/
inline std::string toString() const
{
return sl::toString(sh_);
}
/// Iterator tho the first contour vertex in the polygon.
inline Iterator begin() const
{
return sl::cbegin(sh_);
}
/// Alias to begin()
inline Iterator cbegin() const
{
return sl::cbegin(sh_);
}
/// Iterator to the last contour vertex.
inline Iterator end() const
{
return sl::cend(sh_);
}
/// Alias to end()
inline Iterator cend() const
{
return sl::cend(sh_);
}
/**
* @brief Get a copy of an outer vertex within the carried shape.
*
* Note that the vertex considered here is taken from the original shape
* that this item is constructed from. This means that no transformation is
* applied to the shape in this call.
*
* @param idx The index of the requested vertex.
* @return A copy of the requested vertex.
*/
inline Vertex vertex(unsigned long idx) const
{
return sl::vertex(sh_, idx);
}
/**
* @brief Modify a vertex.
*
* Note that this method will invalidate every cached calculation result
* including polygon offset and transformations.
*
* @param idx The index of the requested vertex.
* @param v The new vertex data.
*/
inline void setVertex(unsigned long idx, const Vertex& v )
{
invalidateCache();
sl::vertex(sh_, idx) = v;
}
/**
* @brief Calculate the shape area.
*
* The method returns absolute value and does not reflect polygon
* orientation. The result is cached, subsequent calls will have very little
* cost.
* @return The shape area in floating point double precision.
*/
inline double area() const {
double ret ;
if(area_cache_valid_) ret = area_cache_;
else {
ret = sl::area(infaltedShape());
area_cache_ = ret;
area_cache_valid_ = true;
}
return ret;
}
inline bool isContourConvex() const {
bool ret = false;
switch(convexity_) {
case Convexity::UNCHECKED:
ret = sl::isConvex(sl::contour(transformedShape()));
convexity_ = ret? Convexity::C_TRUE : Convexity::C_FALSE;
break;
case Convexity::C_TRUE: ret = true; break;
case Convexity::C_FALSE:;
}
return ret;
}
inline bool isHoleConvex(unsigned /*holeidx*/) const {
return false;
}
inline bool areHolesConvex() const {
return false;
}
/// The number of the outer ring vertices.
inline size_t vertexCount() const {
return sl::contourVertexCount(sh_);
}
inline size_t holeCount() const {
return sl::holeCount(sh_);
}
/**
* @brief isPointInside
* @param p
* @return
*/
inline bool isInside(const Vertex& p) const
{
return sl::isInside(p, transformedShape());
}
inline bool isInside(const _Item& sh) const
{
return sl::isInside(transformedShape(), sh.transformedShape());
}
inline bool isInside(const RawShape& sh) const
{
return sl::isInside(transformedShape(), sh);
}
inline bool isInside(const _Box<TPoint<RawShape>>& box) const;
inline bool isInside(const _Circle<TPoint<RawShape>>& box) const;
inline void translate(const Vertex& d) BP2D_NOEXCEPT
{
translation(translation() + d);
}
inline void rotate(const Radians& rads) BP2D_NOEXCEPT
{
rotation(rotation() + rads);
}
inline void inflation(Coord distance) BP2D_NOEXCEPT
{
inflation_ = distance;
has_inflation_ = true;
invalidateCache();
}
inline Coord inflation() const BP2D_NOEXCEPT {
return inflation_;
}
inline void inflate(Coord distance) BP2D_NOEXCEPT
{
inflation(inflation() + distance);
}
inline Radians rotation() const BP2D_NOEXCEPT
{
return rotation_;
}
inline TPoint<RawShape> translation() const BP2D_NOEXCEPT
{
return translation_;
}
inline void rotation(Radians rot) BP2D_NOEXCEPT
{
if(rotation_ != rot) {
rotation_ = rot; has_rotation_ = true; tr_cache_valid_ = false;
rmt_valid_ = false; lmb_valid_ = false;
bb_cache_.valid = false;
}
}
inline void translation(const TPoint<RawShape>& tr) BP2D_NOEXCEPT
{
if(translation_ != tr) {
translation_ = tr; has_translation_ = true; tr_cache_valid_ = false;
//bb_cache_.valid = false;
}
}
inline const RawShape& transformedShape() const
{
if(tr_cache_valid_) return tr_cache_;
RawShape cpy = infaltedShape();
if(has_rotation_) sl::rotate(cpy, rotation_);
if(has_translation_) sl::translate(cpy, translation_);
tr_cache_ = cpy; tr_cache_valid_ = true;
rmt_valid_ = false; lmb_valid_ = false;
return tr_cache_;
}
inline operator RawShape() const
{
return transformedShape();
}
inline const RawShape& rawShape() const BP2D_NOEXCEPT
{
return sh_;
}
inline void resetTransformation() BP2D_NOEXCEPT
{
has_translation_ = false; has_rotation_ = false; has_inflation_ = false;
invalidateCache();
}
inline Box boundingBox() const {
if(!bb_cache_.valid) {
if(!has_rotation_)
bb_cache_.bb = sl::boundingBox(infaltedShape());
else {
// TODO make sure this works
auto rotsh = infaltedShape();
sl::rotate(rotsh, rotation_);
bb_cache_.bb = sl::boundingBox(rotsh);
}
bb_cache_.valid = true;
}
auto &bb = bb_cache_.bb; auto &tr = translation_;
return {bb.minCorner() + tr, bb.maxCorner() + tr };
}
inline Vertex referenceVertex() const {
return rightmostTopVertex();
}
inline Vertex rightmostTopVertex() const {
if(!rmt_valid_ || !tr_cache_valid_) { // find max x and max y vertex
auto& tsh = transformedShape();
rmt_ = std::max_element(sl::cbegin(tsh), sl::cend(tsh), vsort);
rmt_valid_ = true;
}
return *rmt_;
}
inline Vertex leftmostBottomVertex() const {
if(!lmb_valid_ || !tr_cache_valid_) { // find min x and min y vertex
auto& tsh = transformedShape();
lmb_ = std::min_element(sl::cbegin(tsh), sl::cend(tsh), vsort);
lmb_valid_ = true;
}
return *lmb_;
}
//Static methods:
inline static bool intersects(const _Item& sh1, const _Item& sh2)
{
return sl::intersects(sh1.transformedShape(),
sh2.transformedShape());
}
inline static bool touches(const _Item& sh1, const _Item& sh2)
{
return sl::touches(sh1.transformedShape(),
sh2.transformedShape());
}
private:
inline const RawShape& infaltedShape() const {
if(has_inflation_ ) {
if(inflate_cache_valid_) return inflate_cache_;
inflate_cache_ = sh_;
sl::offset(inflate_cache_, inflation_);
inflate_cache_valid_ = true;
return inflate_cache_;
}
return sh_;
}
inline void invalidateCache() const BP2D_NOEXCEPT
{
tr_cache_valid_ = false;
lmb_valid_ = false; rmt_valid_ = false;
area_cache_valid_ = false;
inflate_cache_valid_ = false;
bb_cache_.valid = false;
convexity_ = Convexity::UNCHECKED;
}
static inline bool vsort(const Vertex& v1, const Vertex& v2)
{
TCompute<Vertex> x1 = getX(v1), x2 = getX(v2);
TCompute<Vertex> y1 = getY(v1), y2 = getY(v2);
return y1 == y2 ? x1 < x2 : y1 < y2;
}
};
/**
* \brief Subclass of _Item for regular rectangle items.
*/
template<class RawShape>
class _Rectangle: public _Item<RawShape> {
using _Item<RawShape>::vertex;
using TO = Orientation;
public:
using Unit = TCoord<TPoint<RawShape>>;
template<TO o = OrientationType<RawShape>::Value>
inline _Rectangle(Unit width, Unit height,
// disable this ctor if o != CLOCKWISE
enable_if_t< o == TO::CLOCKWISE, int> = 0 ):
_Item<RawShape>( sl::create<RawShape>( {
{0, 0},
{0, height},
{width, height},
{width, 0},
{0, 0}
} ))
{
}
template<TO o = OrientationType<RawShape>::Value>
inline _Rectangle(Unit width, Unit height,
// disable this ctor if o != COUNTER_CLOCKWISE
enable_if_t< o == TO::COUNTER_CLOCKWISE, int> = 0 ):
_Item<RawShape>( sl::create<RawShape>( {
{0, 0},
{width, 0},
{width, height},
{0, height},
{0, 0}
} ))
{
}
inline Unit width() const BP2D_NOEXCEPT {
return getX(vertex(2));
}
inline Unit height() const BP2D_NOEXCEPT {
return getY(vertex(2));
}
};
template<class RawShape>
inline bool _Item<RawShape>::isInside(const _Box<TPoint<RawShape>>& box) const {
return sl::isInside(boundingBox(), box);
}
template<class RawShape> inline bool
_Item<RawShape>::isInside(const _Circle<TPoint<RawShape>>& circ) const {
return sl::isInside(transformedShape(), circ);
}
template<class RawShape> using _ItemRef = std::reference_wrapper<_Item<RawShape>>;
template<class RawShape> using _ItemGroup = std::vector<_ItemRef<RawShape>>;
/**
* \brief A list of packed item vectors. Each vector represents a bin.
*/
template<class RawShape>
using _PackGroup = std::vector<std::vector<_ItemRef<RawShape>>>;
template<class Iterator>
struct ConstItemRange {
Iterator from;
Iterator to;
bool valid = false;
ConstItemRange() = default;
ConstItemRange(Iterator f, Iterator t): from(f), to(t), valid(true) {}
};
template<class Container>
inline ConstItemRange<typename Container::const_iterator>
rem(typename Container::const_iterator it, const Container& cont) {
return {std::next(it), cont.end()};
}
/**
* \brief A wrapper interface (trait) class for any placement strategy provider.
*
* If a client wants to use its own placement algorithm, all it has to do is to
* specialize this class template and define all the ten methods it has. It can
* use the strategies::PlacerBoilerplace class for creating a new placement
* strategy where only the constructor and the trypack method has to be provided
* and it will work out of the box.
*/
template<class PlacementStrategy>
class PlacementStrategyLike {
PlacementStrategy impl_;
public:
using RawShape = typename PlacementStrategy::ShapeType;
/// The item type that the placer works with.
using Item = _Item<RawShape>;
/// The placer's config type. Should be a simple struct but can be anything.
using Config = typename PlacementStrategy::Config;
/**
* \brief The type of the bin that the placer works with.
*
* Can be a box or an arbitrary shape or just a width or height without a
* second dimension if an infinite bin is considered.
*/
using BinType = typename PlacementStrategy::BinType;
/**
* \brief Pack result that can be used to accept or discard it. See trypack
* method.
*/
using PackResult = typename PlacementStrategy::PackResult;
using ItemGroup = _ItemGroup<RawShape>;
using DefaultIterator = typename ItemGroup::const_iterator;
/**
* @brief Constructor taking the bin and an optional configuration.
* @param bin The bin object whose type is defined by the placement strategy.
* @param config The configuration for the particular placer.
*/
explicit PlacementStrategyLike(const BinType& bin,
const Config& config = Config()):
impl_(bin)
{
configure(config);
}
/**
* @brief Provide a different configuration for the placer.
*
* Note that it depends on the particular placer implementation how it
* reacts to config changes in the middle of a calculation.
*
* @param config The configuration object defined by the placement strategy.
*/
inline void configure(const Config& config) { impl_.configure(config); }
/**
* Try to pack an item with a result object that contains the packing
* information for later accepting it.
*
* \param item_store A container of items that are intended to be packed
* later. Can be used by the placer to switch tactics. When it's knows that
* many items will come a greedy strategy may not be the best.
* \param from The iterator to the item from which the packing should start,
* including the pointed item
* \param count How many items should be packed. If the value is 1, than
* just the item pointed to by "from" argument should be packed.
*/
template<class Iter = DefaultIterator>
inline PackResult trypack(
Item& item,
const ConstItemRange<Iter>& remaining = ConstItemRange<Iter>())
{
return impl_.trypack(item, remaining);
}
/**
* @brief A method to accept a previously tried item (or items).
*
* If the pack result is a failure the method should ignore it.
* @param r The result of a previous trypack call.
*/
inline void accept(PackResult& r) { impl_.accept(r); }
/**
* @brief pack Try to pack and immediately accept it on success.
*
* A default implementation would be to call
* { auto&& r = trypack(...); accept(r); return r; } but we should let the
* implementor of the placement strategy to harvest any optimizations from
* the absence of an intermediate step. The above version can still be used
* in the implementation.
*
* @param item The item to pack.
* @return Returns true if the item was packed or false if it could not be
* packed.
*/
template<class Range = ConstItemRange<DefaultIterator>>
inline bool pack(
Item& item,
const Range& remaining = Range())
{
return impl_.pack(item, remaining);
}
/**
* This method makes possible to "preload" some items into the placer. It
* will not move these items but will consider them as already packed.
*/
inline void preload(const ItemGroup& packeditems)
{
impl_.preload(packeditems);
}
/// Unpack the last element (remove it from the list of packed items).
inline void unpackLast() { impl_.unpackLast(); }
/// Get the bin object.
inline const BinType& bin() const { return impl_.bin(); }
/// Set a new bin object.
inline void bin(const BinType& bin) { impl_.bin(bin); }
/// Get the packed items.
inline ItemGroup getItems() { return impl_.getItems(); }
/// Clear the packed items so a new session can be started.
inline void clearItems() { impl_.clearItems(); }
inline double filledArea() const { return impl_.filledArea(); }
};
// The progress function will be called with the number of placed items
using ProgressFunction = std::function<void(unsigned)>;
using StopCondition = std::function<bool(void)>;
/**
* A wrapper interface (trait) class for any selections strategy provider.
*/
template<class SelectionStrategy>
class SelectionStrategyLike {
SelectionStrategy impl_;
public:
using RawShape = typename SelectionStrategy::ShapeType;
using Item = _Item<RawShape>;
using PackGroup = _PackGroup<RawShape>;
using Config = typename SelectionStrategy::Config;
/**
* @brief Provide a different configuration for the selection strategy.
*
* Note that it depends on the particular placer implementation how it
* reacts to config changes in the middle of a calculation.
*
* @param config The configuration object defined by the selection strategy.
*/
inline void configure(const Config& config) {
impl_.configure(config);
}
/**
* @brief A function callback which should be called whenever an item or
* a group of items where successfully packed.
* @param fn A function callback object taking one unsigned integer as the
* number of the remaining items to pack.
*/
void progressIndicator(ProgressFunction fn) { impl_.progressIndicator(fn); }
void stopCondition(StopCondition cond) { impl_.stopCondition(cond); }
/**
* \brief A method to start the calculation on the input sequence.
*
* \tparam TPlacer The only mandatory template parameter is the type of
* placer compatible with the PlacementStrategyLike interface.
*
* \param first, last The first and last iterator if the input sequence. It
* can be only an iterator of a type convertible to Item.
* \param bin. The shape of the bin. It has to be supported by the placement
* strategy.
* \param An optional config object for the placer.
*/
template<class TPlacer, class TIterator,
class TBin = typename PlacementStrategyLike<TPlacer>::BinType,
class PConfig = typename PlacementStrategyLike<TPlacer>::Config>
inline void packItems(
TIterator first,
TIterator last,
TBin&& bin,
PConfig&& config = PConfig() )
{
impl_.template packItems<TPlacer>(first, last,
std::forward<TBin>(bin),
std::forward<PConfig>(config));
}
/**
* @brief Get the items for a particular bin.
* @param binIndex The index of the requested bin.
* @return Returns a list of all items packed into the requested bin.
*/
inline const PackGroup& getResult() const {
return impl_.getResult();
}
void clear() { impl_.clear(); }
};
/**
* The _Nester is the front-end class for the libnest2d library. It takes the
* input items and changes their transformations to be inside the provided bin.
*/
template<class PlacementStrategy, class SelectionStrategy >
class _Nester {
using TSel = SelectionStrategyLike<SelectionStrategy>;
TSel selector_;
public:
using Item = typename PlacementStrategy::Item;
using ShapeType = typename Item::ShapeType;
using ItemRef = std::reference_wrapper<Item>;
using TPlacer = PlacementStrategyLike<PlacementStrategy>;
using BinType = typename TPlacer::BinType;
using PlacementConfig = typename TPlacer::Config;
using SelectionConfig = typename TSel::Config;
using Coord = TCoord<TPoint<typename Item::ShapeType>>;
using PackGroup = _PackGroup<typename Item::ShapeType>;
using ResultType = PackGroup;
private:
BinType bin_;
PlacementConfig pconfig_;
Coord min_obj_distance_;
using SItem = typename SelectionStrategy::Item;
using TPItem = remove_cvref_t<Item>;
using TSItem = remove_cvref_t<SItem>;
StopCondition stopfn_;
template<class It> using TVal = remove_ref_t<typename It::value_type>;
template<class It, class Out>
using ItemIteratorOnly =
enable_if_t<std::is_convertible<TVal<It>&, TPItem&>::value, Out>;
public:
/**
* \brief Constructor taking the bin as the only mandatory parameter.
*
* \param bin The bin shape that will be used by the placers. The type
* of the bin should be one that is supported by the placer type.
*/
template<class TBinType = BinType,
class PConf = PlacementConfig,
class SConf = SelectionConfig>
_Nester(TBinType&& bin, Coord min_obj_distance = 0,
const PConf& pconfig = PConf(), const SConf& sconfig = SConf()):
bin_(std::forward<TBinType>(bin)),
pconfig_(pconfig),
min_obj_distance_(min_obj_distance)
{
static_assert( std::is_same<TPItem, TSItem>::value,
"Incompatible placement and selection strategy!");
selector_.configure(sconfig);
}
void configure(const PlacementConfig& pconf) { pconfig_ = pconf; }
void configure(const SelectionConfig& sconf) { selector_.configure(sconf); }
void configure(const PlacementConfig& pconf, const SelectionConfig& sconf)
{
pconfig_ = pconf;
selector_.configure(sconf);
}
void configure(const SelectionConfig& sconf, const PlacementConfig& pconf)
{
pconfig_ = pconf;
selector_.configure(sconf);
}
/**
* \brief Arrange an input sequence of _Item-s.
*
* To get the result, call the translation(), rotation() and binId()
* methods of each item. If only the transformed polygon is needed, call
* transformedShape() to get the properly transformed shapes.
*
* The number of groups in the pack group is the number of bins opened by
* the selection algorithm.
*/
template<class It>
inline ItemIteratorOnly<It, size_t> execute(It from, It to)
{
auto infl = static_cast<Coord>(std::ceil(min_obj_distance_/2.0));
if(infl > 0) std::for_each(from, to, [this, infl](Item& item) {
item.inflate(infl);
});
selector_.template packItems<PlacementStrategy>(
from, to, bin_, pconfig_);
if(min_obj_distance_ > 0) std::for_each(from, to, [infl](Item& item) {
item.inflate(-infl);
});
return selector_.getResult().size();
}
/// Set a progress indicator function object for the selector.
inline _Nester& progressIndicator(ProgressFunction func)
{
selector_.progressIndicator(func); return *this;
}
/// Set a predicate to tell when to abort nesting.
inline _Nester& stopCondition(StopCondition fn)
{
stopfn_ = fn; selector_.stopCondition(fn); return *this;
}
inline const PackGroup& lastResult() const
{
return selector_.getResult();
}
};
}
#endif // NESTER_HPP

View File

@ -1,61 +0,0 @@
find_package(NLopt 1.4)
if(NOT NLopt_FOUND)
message(STATUS "NLopt not found so downloading "
"and automatic build is performed...")
include(DownloadProject)
if (CMAKE_VERSION VERSION_LESS 3.2)
set(UPDATE_DISCONNECTED_IF_AVAILABLE "")
else()
set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1")
endif()
set(URL_NLOPT "https://github.com/stevengj/nlopt.git"
CACHE STRING "Location of the nlopt git repository")
# set(NLopt_DIR ${CMAKE_BINARY_DIR}/nlopt)
include(DownloadProject)
download_project( PROJ nlopt
GIT_REPOSITORY ${URL_NLOPT}
GIT_TAG v2.5.0
# CMAKE_CACHE_ARGS -DBUILD_SHARED_LIBS:BOOL=OFF -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${NLopt_DIR}
${UPDATE_DISCONNECTED_IF_AVAILABLE}
)
set(SHARED_LIBS_STATE BUILD_SHARED_LIBS)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(NLOPT_PYTHON OFF CACHE BOOL "" FORCE)
set(NLOPT_OCTAVE OFF CACHE BOOL "" FORCE)
set(NLOPT_MATLAB OFF CACHE BOOL "" FORCE)
set(NLOPT_GUILE OFF CACHE BOOL "" FORCE)
set(NLOPT_SWIG OFF CACHE BOOL "" FORCE)
set(NLOPT_LINK_PYTHON OFF CACHE BOOL "" FORCE)
add_subdirectory(${nlopt_SOURCE_DIR} ${nlopt_BINARY_DIR})
set(NLopt_LIBS nlopt)
set(NLopt_INCLUDE_DIR ${nlopt_BINARY_DIR} ${nlopt_BINARY_DIR}/src/api)
set(SHARED_LIBS_STATE ${SHARED_STATE})
add_library(nloptOptimizer INTERFACE)
target_link_libraries(nloptOptimizer INTERFACE nlopt)
target_include_directories(nloptOptimizer INTERFACE ${NLopt_INCLUDE_DIR})
else()
add_library(nloptOptimizer INTERFACE)
target_link_libraries(nloptOptimizer INTERFACE Nlopt::Nlopt)
endif()
#target_sources( nloptOptimizer INTERFACE
#${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp
#${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp
#${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp
#${CMAKE_CURRENT_SOURCE_DIR}/nlopt_boilerplate.hpp
#)
target_compile_definitions(nloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT)
# And finally plug the nloptOptimizer into libnest2d
#target_link_libraries(libnest2d INTERFACE nloptOptimizer)

View File

@ -1,7 +1,7 @@
#ifndef PLACER_BOILERPLATE_HPP
#define PLACER_BOILERPLATE_HPP
#include <libnest2d/libnest2d.hpp>
#include <libnest2d/nester.hpp>
namespace libnest2d { namespace placers {

View File

@ -2,7 +2,7 @@
#define SELECTION_BOILERPLATE_HPP
#include <atomic>
#include <libnest2d/libnest2d.hpp>
#include <libnest2d/nester.hpp>
namespace libnest2d { namespace selections {

View File

@ -1,19 +1,24 @@
#include <libnest2d.h>
#include <libnest2d/libnest2d.hpp>
namespace libnest2d {
template class Nester<NfpPlacer, FirstFitSelection>;
template class Nester<BottomLeftPlacer, FirstFitSelection>;
template class _Nester<NfpPlacer, FirstFitSelection>;
template class _Nester<BottomLeftPlacer, FirstFitSelection>;
template std::size_t _Nester<NfpPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
template std::size_t _Nester<BottomLeftPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator from to,
std::vector<Item>::iterator to,
const Box & bin,
Coord dist,
const NestConfig<NfpPlacer, FirstFitSelection> &cfg,
NestControl ctl);
template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator from to,
std::vector<Item>::iterator to,
const Box & bin,
Coord dist,
const NestConfig<BottomLeftPlacer, FirstFitSelection> &cfg,

View File

@ -1,60 +0,0 @@
# Try to find existing GTest installation
find_package(GTest 1.7)
if(NOT GTEST_FOUND)
set(URL_GTEST "https://github.com/google/googletest.git"
CACHE STRING "Google test source code repository location.")
message(STATUS "GTest not found so downloading from ${URL_GTEST}")
# Go and download google test framework, integrate it with the build
set(GTEST_LIBS_TO_LINK gtest gtest_main)
if (CMAKE_VERSION VERSION_LESS 3.2)
set(UPDATE_DISCONNECTED_IF_AVAILABLE "")
else()
set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1")
endif()
include(DownloadProject)
download_project(PROJ googletest
GIT_REPOSITORY ${URL_GTEST}
GIT_TAG release-1.7.0
${UPDATE_DISCONNECTED_IF_AVAILABLE}
)
# Prevent GoogleTest from overriding our compiler/linker options
# when building with Visual Studio
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
add_subdirectory(${googletest_SOURCE_DIR}
${googletest_BINARY_DIR}
)
set(GTEST_INCLUDE_DIRS ${googletest_SOURCE_DIR}/include)
else()
find_package(Threads REQUIRED)
set(GTEST_LIBS_TO_LINK ${GTEST_BOTH_LIBRARIES} Threads::Threads)
endif()
add_executable(tests_clipper_nlopt
test.cpp
../tools/svgtools.hpp
# ../tools/libnfpglue.hpp
# ../tools/libnfpglue.cpp
printer_parts.h
printer_parts.cpp
)
target_link_libraries(tests_clipper_nlopt libnest2d ${GTEST_LIBS_TO_LINK} )
target_include_directories(tests_clipper_nlopt PRIVATE BEFORE ${GTEST_INCLUDE_DIRS})
if(NOT LIBNEST2D_HEADER_ONLY)
target_link_libraries(tests_clipper_nlopt ${LIBNAME})
else()
target_link_libraries(tests_clipper_nlopt libnest2d)
endif()
add_test(libnest2d_tests tests_clipper_nlopt)

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +0,0 @@
#ifndef PRINTER_PARTS_H
#define PRINTER_PARTS_H
#include <vector>
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
using TestData = std::vector<ClipperLib::Path>;
using TestDataEx = std::vector<ClipperLib::Polygon>;
extern const TestData PRINTER_PART_POLYGONS;
extern const TestData STEGOSAUR_POLYGONS;
extern const TestDataEx PRINTER_PART_POLYGONS_EX;
#endif // PRINTER_PARTS_H

File diff suppressed because it is too large Load Diff

View File

@ -204,7 +204,7 @@ if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
endif ()
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB)
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNEST2D_INCLUDES} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(libslic3r
libnest2d
@ -221,7 +221,7 @@ target_link_libraries(libslic3r
poly2tri
qhull
semver
tbb
TBB::tbb
${CMAKE_DL_LIBS}
)

View File

@ -11,7 +11,7 @@
#include <map>
#endif
#include "libslic3r/Utils.hpp"
// #include "libslic3r/Utils.hpp"
namespace Slic3r {
namespace Utils {

View File

@ -10,23 +10,14 @@ target_include_directories(Catch2 INTERFACE ${CMAKE_CURRENT_LIST_DIR})
add_library(Catch2::Catch2 ALIAS Catch2)
include(Catch)
add_library(test_catch2_common INTERFACE)
target_compile_definitions(test_catch2_common INTERFACE TEST_DATA_DIR=R"\(${TEST_DATA_DIR}\)" CATCH_CONFIG_FAST_COMPILE)
target_link_libraries(test_catch2_common INTERFACE Catch2::Catch2)
add_library(test_common INTERFACE)
target_compile_definitions(test_common INTERFACE TEST_DATA_DIR=R"\(${TEST_DATA_DIR}\)" CATCH_CONFIG_FAST_COMPILE)
target_link_libraries(test_common INTERFACE Catch2::Catch2)
if (APPLE)
target_link_libraries(test_common INTERFACE "-liconv -framework IOKit" "-framework CoreFoundation" -lc++)
endif()
target_link_libraries(test_common INTERFACE test_catch2_common)
# DEPRECATED:
#find_package(GTest REQUIRED)
#add_library(test_gtest_common INTERFACE)
#target_compile_definitions(test_gtest_common INTERFACE TEST_DATA_DIR=R"\(${TEST_DATA_DIR}\)")
#target_link_libraries(test_gtest_common INTERFACE GTest::GTest GTest::Main)
add_subdirectory(libnest2d)
add_subdirectory(timeutils)
add_subdirectory(sla_print)

View File

@ -1,5 +1,7 @@
get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp)
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r ${Boost_LIBRARIES} ${TBB_LIBRARIES} ${Boost_LIBRARIES})
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r
#${Boost_LIBRARIES} ${TBB_LIBRARIES} ${Boost_LIBRARIES}
)
catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")

View File

@ -1,6 +1,6 @@
get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp printer_parts.cpp printer_parts.hpp)
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r ${Boost_LIBRARIES} ${TBB_LIBRARIES} ${Boost_LIBRARIES})
target_link_libraries(${_TEST_NAME}_tests test_common libnest2d )
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "--durations yes")

View File

@ -3,7 +3,7 @@
#include <fstream>
#include <libnest2d.h>
#include <libnest2d/libnest2d.hpp>
#include "printer_parts.hpp"
//#include <libnest2d/geometry_traits_nfp.hpp>
#include "../tools/svgtools.hpp"

View File

@ -1,6 +1,6 @@
get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests.cpp)
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r ${Boost_LIBRARIES} ${TBB_LIBRARIES} ${Boost_LIBRARIES})
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
#catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "--durations yes")

View File

@ -1,6 +1,10 @@
get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp)
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r ${Boost_LIBRARIES} ${TBB_LIBRARIES} ${Boost_LIBRARIES})
add_executable(${_TEST_NAME}_tests
${_TEST_NAME}_tests_main.cpp
${PROJECT_SOURCE_DIR}/src/libslic3r/Time.cpp
${PROJECT_SOURCE_DIR}/src/libslic3r/Time.hpp
)
target_link_libraries(${_TEST_NAME}_tests test_common)
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "--durations yes")