Merge remote-tracking branch 'origin/master' into ys_color_print_extension
This commit is contained in:
commit
4fa2567ea2
58 changed files with 3729 additions and 5967 deletions
|
@ -174,9 +174,10 @@ if (NOT MSVC AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMP
|
|||
add_compile_options(-Werror=return-type)
|
||||
|
||||
#removes LOTS of extraneous Eigen warnings (GCC only supports it since 6.1)
|
||||
#if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 6.1)
|
||||
# add_compile_options(-Wno-ignored-attributes) # Tamas: Eigen include dirs are marked as SYSTEM
|
||||
#endif()
|
||||
#https://eigen.tuxfamily.org/bz/show_bug.cgi?id=1221
|
||||
if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 6.1)
|
||||
add_compile_options(-Wno-ignored-attributes)
|
||||
endif()
|
||||
|
||||
#GCC generates loads of -Wunknown-pragmas when compiling igl. The fix is not easy due to a bug in gcc, see
|
||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66943 or
|
||||
|
@ -291,14 +292,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 +376,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)
|
||||
|
|
|
@ -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,15 +47,14 @@ 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).")
|
||||
else()
|
||||
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})
|
|
@ -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,23 @@ 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()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
find_package(Threads QUIET REQUIRED)
|
||||
set_target_properties(TBB::tbb PROPERTIES INTERFACE_LINK_LIBRARIES "${CMAKE_DL_LIBS};Threads::Threads")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 USE_TBB LIBNEST2D_STATIC LIBNEST2D_OPTIMIZER_nlopt LIBNEST2D_GEOMETRIES_clipper)
|
||||
|
|
|
@ -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})
|
|
@ -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 ""
|
||||
)
|
|
@ -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()
|
|
@ -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()
|
|
@ -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
|
|
@ -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)
|
||||
|
|
@ -299,9 +299,456 @@ inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
|
|||
|
||||
template<class RawShape>
|
||||
NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
|
||||
const RawShape& cother)
|
||||
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
|
||||
|
|
|
@ -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;
|
||||
using Point = PointImpl;
|
||||
using Coord = TCoord<PointImpl>;
|
||||
using Box = _Box<PointImpl>;
|
||||
using Segment = _Segment<PointImpl>;
|
||||
using Circle = _Circle<PointImpl>;
|
||||
|
||||
/**
|
||||
* \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 Item = _Item<PolygonImpl>;
|
||||
using Rectangle = _Rectangle<PolygonImpl>;
|
||||
using PackGroup = _PackGroup<PolygonImpl>;
|
||||
|
||||
using VertexConstIterator = typename TContour<RawShape>::const_iterator;
|
||||
using FillerSelection = selections::_FillerSelection<PolygonImpl>;
|
||||
using FirstFitSelection = selections::_FirstFitSelection<PolygonImpl>;
|
||||
using DJDHeuristic = selections::_DJDHeuristic<PolygonImpl>;
|
||||
|
||||
// The original shape that gets encapsulated.
|
||||
RawShape sh_;
|
||||
template<class Bin> // Generic placer for arbitrary bin types
|
||||
using _NfpPlacer = placers::_NofitPolyPlacer<PolygonImpl, Bin>;
|
||||
|
||||
// Transformation data
|
||||
Vertex translation_{0, 0};
|
||||
Radians rotation_{0.0};
|
||||
Coord inflation_{0};
|
||||
// NfpPlacer is with Box bin
|
||||
using NfpPlacer = _NfpPlacer<Box>;
|
||||
|
||||
// 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;
|
||||
// This supports only box shaped bins
|
||||
using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>;
|
||||
|
||||
// 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;
|
||||
#ifdef LIBNEST2D_STATIC
|
||||
|
||||
enum class Convexity: char {
|
||||
UNCHECKED,
|
||||
C_TRUE,
|
||||
C_FALSE
|
||||
};
|
||||
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);
|
||||
|
||||
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_;
|
||||
#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;
|
||||
|
||||
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;
|
||||
}
|
||||
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:
|
||||
|
||||
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));
|
||||
}
|
||||
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 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
|
||||
|
|
869
src/libnest2d/include/libnest2d/nester.hpp
Normal file
869
src/libnest2d/include/libnest2d/nester.hpp
Normal 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
|
|
@ -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)
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef PLACER_BOILERPLATE_HPP
|
||||
#define PLACER_BOILERPLATE_HPP
|
||||
|
||||
#include <libnest2d/libnest2d.hpp>
|
||||
#include <libnest2d/nester.hpp>
|
||||
|
||||
namespace libnest2d { namespace placers {
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#define SELECTION_BOILERPLATE_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <libnest2d/libnest2d.hpp>
|
||||
#include <libnest2d/nester.hpp>
|
||||
|
||||
namespace libnest2d { namespace selections {
|
||||
|
||||
|
@ -25,7 +25,7 @@ public:
|
|||
inline void clear() { packed_bins_.clear(); }
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
template<class Placer, class Container, class Bin, class PCfg>
|
||||
void remove_unpackable_items(Container &c, const Bin &bin, const PCfg& pcfg)
|
||||
{
|
||||
|
@ -33,14 +33,14 @@ protected:
|
|||
// then it should be removed from the list
|
||||
auto it = c.begin();
|
||||
while (it != c.end() && !stopcond_()) {
|
||||
|
||||
|
||||
// WARNING: The copy of itm needs to be created before Placer.
|
||||
// Placer is working with references and its destructor still
|
||||
// manipulates the item this is why the order of stack creation
|
||||
// matters here.
|
||||
// matters here.
|
||||
const Item& itm = *it;
|
||||
Item cpy{itm};
|
||||
|
||||
|
||||
Placer p{bin};
|
||||
p.configure(pcfg);
|
||||
if (itm.area() <= 0 || !p.pack(cpy)) it = c.erase(it);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
@ -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
|
@ -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}
|
||||
)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef slic3r_CoolingBuffer_hpp_
|
||||
#define slic3r_CoolingBuffer_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "../libslic3r.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@ public:
|
|||
float value(Axis axis) const { return m_axis[axis]; }
|
||||
bool has(char axis) const;
|
||||
bool has_value(char axis, float &value) const;
|
||||
float new_X(const GCodeReader &reader) const { return this->has(X) ? this->x() : reader.x(); }
|
||||
float new_Y(const GCodeReader &reader) const { return this->has(Y) ? this->y() : reader.y(); }
|
||||
float new_Z(const GCodeReader &reader) const { return this->has(Z) ? this->z() : reader.z(); }
|
||||
float new_E(const GCodeReader &reader) const { return this->has(E) ? this->e() : reader.e(); }
|
||||
float new_F(const GCodeReader &reader) const { return this->has(F) ? this->f() : reader.f(); }
|
||||
|
|
|
@ -269,7 +269,7 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s
|
|||
assert(F > 0.);
|
||||
assert(F < 100000.);
|
||||
std::ostringstream gcode;
|
||||
gcode << "G1 F" << F;
|
||||
gcode << "G1 F" << XYZF_NUM(F);
|
||||
COMMENT(comment);
|
||||
gcode << cooling_marker;
|
||||
gcode << "\n";
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <cmath>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <stack>
|
||||
|
@ -16,6 +17,7 @@
|
|||
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
#include "SVG.hpp"
|
||||
|
@ -335,6 +337,93 @@ double rad2deg_dir(double angle)
|
|||
return rad2deg(angle);
|
||||
}
|
||||
|
||||
Point circle_taubin_newton(const Points::const_iterator& input_begin, const Points::const_iterator& input_end, size_t cycles)
|
||||
{
|
||||
Vec2ds tmp;
|
||||
tmp.reserve(std::distance(input_begin, input_end));
|
||||
std::transform(input_begin, input_end, std::back_inserter(tmp), [] (const Point& in) { return unscale(in); } );
|
||||
Vec2d center = circle_taubin_newton(tmp.cbegin(), tmp.end(), cycles);
|
||||
return Point::new_scale(center.x(), center.y());
|
||||
}
|
||||
|
||||
/// Adapted from work in "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126
|
||||
/// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end
|
||||
/// lie on.
|
||||
Vec2d circle_taubin_newton(const Vec2ds::const_iterator& input_begin, const Vec2ds::const_iterator& input_end, size_t cycles)
|
||||
{
|
||||
// calculate the centroid of the data set
|
||||
const Vec2d sum = std::accumulate(input_begin, input_end, Vec2d(0,0));
|
||||
const size_t n = std::distance(input_begin, input_end);
|
||||
const double n_flt = static_cast<double>(n);
|
||||
const Vec2d centroid { sum / n_flt };
|
||||
|
||||
// Compute the normalized moments of the data set.
|
||||
double Mxx = 0, Myy = 0, Mxy = 0, Mxz = 0, Myz = 0, Mzz = 0;
|
||||
for (auto it = input_begin; it < input_end; ++it) {
|
||||
// center/normalize the data.
|
||||
double Xi {it->x() - centroid.x()};
|
||||
double Yi {it->y() - centroid.y()};
|
||||
double Zi {Xi*Xi + Yi*Yi};
|
||||
Mxy += (Xi*Yi);
|
||||
Mxx += (Xi*Xi);
|
||||
Myy += (Yi*Yi);
|
||||
Mxz += (Xi*Zi);
|
||||
Myz += (Yi*Zi);
|
||||
Mzz += (Zi*Zi);
|
||||
}
|
||||
|
||||
// divide by number of points to get the moments
|
||||
Mxx /= n_flt;
|
||||
Myy /= n_flt;
|
||||
Mxy /= n_flt;
|
||||
Mxz /= n_flt;
|
||||
Myz /= n_flt;
|
||||
Mzz /= n_flt;
|
||||
|
||||
// Compute the coefficients of the characteristic polynomial for the circle
|
||||
// eq 5.60
|
||||
const double Mz {Mxx + Myy}; // xx + yy = z
|
||||
const double Cov_xy {Mxx*Myy - Mxy*Mxy}; // this shows up a couple times so cache it here.
|
||||
const double C3 {4.0*Mz};
|
||||
const double C2 {-3.0*(Mz*Mz) - Mzz};
|
||||
const double C1 {Mz*(Mzz - (Mz*Mz)) + 4.0*Mz*Cov_xy - (Mxz*Mxz) - (Myz*Myz)};
|
||||
const double C0 {(Mxz*Mxz)*Myy + (Myz*Myz)*Mxx - 2.0*Mxz*Myz*Mxy - Cov_xy*(Mzz - (Mz*Mz))};
|
||||
|
||||
const double C22 = {C2 + C2};
|
||||
const double C33 = {C3 + C3 + C3};
|
||||
|
||||
// solve the characteristic polynomial with Newton's method.
|
||||
double xnew = 0.0;
|
||||
double ynew = 1e20;
|
||||
|
||||
for (size_t i = 0; i < cycles; ++i) {
|
||||
const double yold {ynew};
|
||||
ynew = C0 + xnew * (C1 + xnew*(C2 + xnew * C3));
|
||||
if (std::abs(ynew) > std::abs(yold)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Geometry: Fit is going in the wrong direction.\n";
|
||||
return Vec2d(std::nan(""), std::nan(""));
|
||||
}
|
||||
const double Dy {C1 + xnew*(C22 + xnew*C33)};
|
||||
|
||||
const double xold {xnew};
|
||||
xnew = xold - (ynew / Dy);
|
||||
|
||||
if (std::abs((xnew-xold) / xnew) < 1e-12) i = cycles; // converged, we're done here
|
||||
|
||||
if (xnew < 0) {
|
||||
// reset, we went negative
|
||||
xnew = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// compute the determinant and the circle's parameters now that we've solved.
|
||||
double DET = xnew*xnew - xnew*Mz + Cov_xy;
|
||||
|
||||
Vec2d center(Mxz * (Myy - xnew) - Myz * Mxy, Myz * (Mxx - xnew) - Mxz*Mxy);
|
||||
center /= (DET * 2.);
|
||||
return center + centroid;
|
||||
}
|
||||
|
||||
void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval)
|
||||
{
|
||||
Polygons pp;
|
||||
|
|
|
@ -162,6 +162,15 @@ template<typename T> T angle_to_0_2PI(T angle)
|
|||
|
||||
return angle;
|
||||
}
|
||||
|
||||
/// Find the center of the circle corresponding to the vector of Points as an arc.
|
||||
Point circle_taubin_newton(const Points::const_iterator& input_start, const Points::const_iterator& input_end, size_t cycles = 20);
|
||||
inline Point circle_taubin_newton(const Points& input, size_t cycles = 20) { return circle_taubin_newton(input.cbegin(), input.cend(), cycles); }
|
||||
|
||||
/// Find the center of the circle corresponding to the vector of Pointfs as an arc.
|
||||
Vec2d circle_taubin_newton(const Vec2ds::const_iterator& input_start, const Vec2ds::const_iterator& input_end, size_t cycles = 20);
|
||||
inline Vec2d circle_taubin_newton(const Vec2ds& input, size_t cycles = 20) { return circle_taubin_newton(input.cbegin(), input.cend(), cycles); }
|
||||
|
||||
void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval);
|
||||
|
||||
double linint(double value, double oldmin, double oldmax, double newmin, double newmax);
|
||||
|
|
|
@ -86,10 +86,7 @@ bool Line::intersection(const Line &l2, Point *intersection) const
|
|||
const Line &l1 = *this;
|
||||
const Vec2d v1 = (l1.b - l1.a).cast<double>();
|
||||
const Vec2d v2 = (l2.b - l2.a).cast<double>();
|
||||
const Vec2d v12 = (l1.a - l2.a).cast<double>();
|
||||
double denom = cross2(v1, v2);
|
||||
double nume_a = cross2(v2, v12);
|
||||
double nume_b = cross2(v1, v12);
|
||||
if (fabs(denom) < EPSILON)
|
||||
#if 0
|
||||
// Lines are collinear. Return true if they are coincident (overlappign).
|
||||
|
@ -97,6 +94,9 @@ bool Line::intersection(const Line &l2, Point *intersection) const
|
|||
#else
|
||||
return false;
|
||||
#endif
|
||||
const Vec2d v12 = (l1.a - l2.a).cast<double>();
|
||||
double nume_a = cross2(v2, v12);
|
||||
double nume_b = cross2(v1, v12);
|
||||
double t1 = nume_a / denom;
|
||||
double t2 = nume_b / denom;
|
||||
if (t1 >= 0 && t1 <= 1.0f && t2 >= 0 && t2 <= 1.0f) {
|
||||
|
|
|
@ -38,6 +38,7 @@ typedef std::vector<Point*> PointPtrs;
|
|||
typedef std::vector<const Point*> PointConstPtrs;
|
||||
typedef std::vector<Vec3crd> Points3;
|
||||
typedef std::vector<Vec2d> Pointfs;
|
||||
typedef std::vector<Vec2d> Vec2ds;
|
||||
typedef std::vector<Vec3d> Pointf3s;
|
||||
|
||||
typedef Eigen::Matrix<float, 2, 2, Eigen::DontAlign> Matrix2f;
|
||||
|
@ -87,12 +88,13 @@ class Point : public Vec2crd
|
|||
public:
|
||||
typedef coord_t coord_type;
|
||||
|
||||
Point() : Vec2crd() { (*this)(0) = 0; (*this)(1) = 0; }
|
||||
Point(coord_t x, coord_t y) { (*this)(0) = x; (*this)(1) = y; }
|
||||
Point(int64_t x, int64_t y) { (*this)(0) = coord_t(x); (*this)(1) = coord_t(y); } // for Clipper
|
||||
Point(double x, double y) { (*this)(0) = coord_t(lrint(x)); (*this)(1) = coord_t(lrint(y)); }
|
||||
Point() : Vec2crd(0, 0) {}
|
||||
Point(coord_t x, coord_t y) : Vec2crd(x, y) {}
|
||||
Point(int64_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {} // for Clipper
|
||||
Point(double x, double y) : Vec2crd(coord_t(lrint(x)), coord_t(lrint(y))) {}
|
||||
Point(const Point &rhs) { *this = rhs; }
|
||||
// This constructor allows you to construct Point from Eigen expressions
|
||||
explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(lrint(rhs.x())), coord_t(lrint(rhs.y()))) {}
|
||||
// This constructor allows you to construct Point from Eigen expressions
|
||||
template<typename OtherDerived>
|
||||
Point(const Eigen::MatrixBase<OtherDerived> &other) : Vec2crd(other) {}
|
||||
static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); }
|
||||
|
@ -126,6 +128,36 @@ public:
|
|||
Point projection_onto(const Line &line) const;
|
||||
};
|
||||
|
||||
inline bool is_approx(const Point &p1, const Point &p2, coord_t epsilon = coord_t(SCALED_EPSILON))
|
||||
{
|
||||
Point d = (p2 - p1).cwiseAbs();
|
||||
return d.x() < epsilon && d.y() < epsilon;
|
||||
}
|
||||
|
||||
inline bool is_approx(const Vec2f &p1, const Vec2f &p2, float epsilon = float(EPSILON))
|
||||
{
|
||||
Vec2f d = (p2 - p1).cwiseAbs();
|
||||
return d.x() < epsilon && d.y() < epsilon;
|
||||
}
|
||||
|
||||
inline bool is_approx(const Vec2d &p1, const Vec2d &p2, double epsilon = EPSILON)
|
||||
{
|
||||
Vec2d d = (p2 - p1).cwiseAbs();
|
||||
return d.x() < epsilon && d.y() < epsilon;
|
||||
}
|
||||
|
||||
inline bool is_approx(const Vec3f &p1, const Vec3f &p2, float epsilon = float(EPSILON))
|
||||
{
|
||||
Vec3f d = (p2 - p1).cwiseAbs();
|
||||
return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon;
|
||||
}
|
||||
|
||||
inline bool is_approx(const Vec3d &p1, const Vec3d &p2, double epsilon = EPSILON)
|
||||
{
|
||||
Vec3d d = (p2 - p1).cwiseAbs();
|
||||
return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon;
|
||||
}
|
||||
|
||||
namespace int128 {
|
||||
// Exact orientation predicate,
|
||||
// returns +1: CCW, 0: collinear, -1: CW.
|
||||
|
|
|
@ -15,7 +15,7 @@ Polyline Polygon::split_at_vertex(const Point &point) const
|
|||
// find index of point
|
||||
for (const Point &pt : this->points)
|
||||
if (pt == point)
|
||||
return this->split_at_index(&pt - &this->points.front());
|
||||
return this->split_at_index(int(&pt - &this->points.front()));
|
||||
throw std::invalid_argument("Point not found");
|
||||
return Polyline();
|
||||
}
|
||||
|
@ -175,16 +175,16 @@ Point Polygon::centroid() const
|
|||
Points Polygon::concave_points(double angle) const
|
||||
{
|
||||
Points points;
|
||||
angle = 2*PI - angle;
|
||||
angle = 2. * PI - angle + EPSILON;
|
||||
|
||||
// check whether first point forms a concave angle
|
||||
if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) <= angle)
|
||||
points.push_back(this->points.front());
|
||||
|
||||
// check whether points 1..(n-1) form concave angles
|
||||
for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) {
|
||||
if (p->ccw_angle(*(p-1), *(p+1)) <= angle) points.push_back(*p);
|
||||
}
|
||||
for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++ p)
|
||||
if (p->ccw_angle(*(p-1), *(p+1)) <= angle)
|
||||
points.push_back(*p);
|
||||
|
||||
// check whether last point forms a concave angle
|
||||
if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) <= angle)
|
||||
|
@ -198,7 +198,7 @@ Points Polygon::concave_points(double angle) const
|
|||
Points Polygon::convex_points(double angle) const
|
||||
{
|
||||
Points points;
|
||||
angle = 2*PI - angle;
|
||||
angle = 2*PI - angle - EPSILON;
|
||||
|
||||
// check whether first point forms a convex angle
|
||||
if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) >= angle)
|
||||
|
@ -394,4 +394,45 @@ bool remove_small(Polygons &polys, double min_area)
|
|||
return modified;
|
||||
}
|
||||
|
||||
void remove_collinear(Polygon &poly)
|
||||
{
|
||||
if (poly.points.size() > 2) {
|
||||
// copy points and append both 1 and last point in place to cover the boundaries
|
||||
Points pp;
|
||||
pp.reserve(poly.points.size()+2);
|
||||
pp.push_back(poly.points.back());
|
||||
pp.insert(pp.begin()+1, poly.points.begin(), poly.points.end());
|
||||
pp.push_back(poly.points.front());
|
||||
// delete old points vector. Will be re-filled in the loop
|
||||
poly.points.clear();
|
||||
|
||||
size_t i = 0;
|
||||
size_t k = 0;
|
||||
while (i < pp.size()-2) {
|
||||
k = i+1;
|
||||
const Point &p1 = pp[i];
|
||||
while (k < pp.size()-1) {
|
||||
const Point &p2 = pp[k];
|
||||
const Point &p3 = pp[k+1];
|
||||
Line l(p1, p3);
|
||||
if(l.distance_to(p2) < SCALED_EPSILON) {
|
||||
k++;
|
||||
} else {
|
||||
if(i > 0) poly.points.push_back(p1); // implicitly removes the first point we appended above
|
||||
i = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(k > pp.size()-2) break; // all remaining points are collinear and can be skipped
|
||||
}
|
||||
poly.points.push_back(pp[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void remove_collinear(Polygons &polys)
|
||||
{
|
||||
for (Polygon &poly : polys)
|
||||
remove_collinear(poly);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -86,6 +86,8 @@ extern bool remove_sticks(Polygons &polys);
|
|||
// Remove polygons with less than 3 edges.
|
||||
extern bool remove_degenerate(Polygons &polys);
|
||||
extern bool remove_small(Polygons &polys, double min_area);
|
||||
extern void remove_collinear(Polygon &poly);
|
||||
extern void remove_collinear(Polygons &polys);
|
||||
|
||||
// Append a vector of polygons at the end of another vector of polygons.
|
||||
inline void polygons_append(Polygons &dst, const Polygons &src) { dst.insert(dst.end(), src.begin(), src.end()); }
|
||||
|
|
|
@ -29,7 +29,7 @@ PrintConfigDef::PrintConfigDef()
|
|||
this->init_common_params();
|
||||
assign_printer_technology_to_unknown(this->options, ptAny);
|
||||
this->init_fff_params();
|
||||
this->init_extruder_retract_keys();
|
||||
this->init_extruder_option_keys();
|
||||
assign_printer_technology_to_unknown(this->options, ptFFF);
|
||||
this->init_sla_params();
|
||||
assign_printer_technology_to_unknown(this->options, ptSLA);
|
||||
|
@ -2270,8 +2270,17 @@ void PrintConfigDef::init_fff_params()
|
|||
}
|
||||
}
|
||||
|
||||
void PrintConfigDef::init_extruder_retract_keys()
|
||||
void PrintConfigDef::init_extruder_option_keys()
|
||||
{
|
||||
// ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings
|
||||
m_extruder_option_keys = {
|
||||
"nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset",
|
||||
"retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed",
|
||||
"retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe",
|
||||
"retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour",
|
||||
"default_filament_profile"
|
||||
};
|
||||
|
||||
m_extruder_retract_keys = {
|
||||
"deretract_speed",
|
||||
"retract_before_travel",
|
||||
|
@ -2938,6 +2947,20 @@ void DynamicPrintConfig::normalize()
|
|||
}
|
||||
}
|
||||
|
||||
void DynamicPrintConfig::set_num_extruders(unsigned int num_extruders)
|
||||
{
|
||||
const auto &defaults = FullPrintConfig::defaults();
|
||||
for (const std::string &key : print_config_def.extruder_option_keys()) {
|
||||
if (key == "default_filament_profile")
|
||||
continue;
|
||||
auto *opt = this->option(key, false);
|
||||
assert(opt != nullptr);
|
||||
assert(opt->is_vector());
|
||||
if (opt != nullptr && opt->is_vector())
|
||||
static_cast<ConfigOptionVectorBase*>(opt)->resize(num_extruders, defaults.option(key));
|
||||
}
|
||||
}
|
||||
|
||||
std::string DynamicPrintConfig::validate()
|
||||
{
|
||||
// Full print config is initialized from the defaults.
|
||||
|
|
|
@ -193,6 +193,8 @@ public:
|
|||
|
||||
static void handle_legacy(t_config_option_key &opt_key, std::string &value);
|
||||
|
||||
// Array options growing with the number of extruders
|
||||
const std::vector<std::string>& extruder_option_keys() const { return m_extruder_option_keys; }
|
||||
// Options defining the extruder retract properties. These keys are sorted lexicographically.
|
||||
// The extruder retract keys could be overidden by the same values defined at the Filament level
|
||||
// (then the key is further prefixed with the "filament_" prefix).
|
||||
|
@ -201,9 +203,10 @@ public:
|
|||
private:
|
||||
void init_common_params();
|
||||
void init_fff_params();
|
||||
void init_extruder_retract_keys();
|
||||
void init_extruder_option_keys();
|
||||
void init_sla_params();
|
||||
|
||||
std::vector<std::string> m_extruder_option_keys;
|
||||
std::vector<std::string> m_extruder_retract_keys;
|
||||
};
|
||||
|
||||
|
@ -231,6 +234,8 @@ public:
|
|||
|
||||
void normalize();
|
||||
|
||||
void set_num_extruders(unsigned int num_extruders);
|
||||
|
||||
// Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned.
|
||||
std::string validate();
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include <map>
|
||||
#endif
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
// #include "libslic3r/Utils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Utils {
|
||||
|
|
|
@ -593,6 +593,16 @@ TriangleMesh TriangleMesh::convex_hull_3d() const
|
|||
return output_mesh;
|
||||
}
|
||||
|
||||
std::vector<ExPolygons> TriangleMesh::slice(const std::vector<double> &z)
|
||||
{
|
||||
// convert doubles to floats
|
||||
std::vector<float> z_f(z.begin(), z.end());
|
||||
TriangleMeshSlicer mslicer(this);
|
||||
std::vector<ExPolygons> layers;
|
||||
mslicer.slice(z_f, 0.0004f, &layers, [](){});
|
||||
return layers;
|
||||
}
|
||||
|
||||
void TriangleMesh::require_shared_vertices()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start";
|
||||
|
@ -1861,7 +1871,8 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
|
|||
}
|
||||
|
||||
// Generate the vertex list for a cube solid of arbitrary size in X/Y/Z.
|
||||
TriangleMesh make_cube(double x, double y, double z) {
|
||||
TriangleMesh make_cube(double x, double y, double z)
|
||||
{
|
||||
Vec3d pv[8] = {
|
||||
Vec3d(x, y, 0), Vec3d(x, 0, 0), Vec3d(0, 0, 0),
|
||||
Vec3d(0, y, 0), Vec3d(x, y, z), Vec3d(0, y, z),
|
||||
|
@ -1878,7 +1889,8 @@ TriangleMesh make_cube(double x, double y, double z) {
|
|||
Pointf3s vertices(&pv[0], &pv[0]+8);
|
||||
|
||||
TriangleMesh mesh(vertices ,facets);
|
||||
return mesh;
|
||||
mesh.repair();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// Generate the mesh for a cylinder and return it, using
|
||||
|
@ -1922,7 +1934,9 @@ TriangleMesh make_cylinder(double r, double h, double fa)
|
|||
facets.emplace_back(Vec3crd(id, 2, 3));
|
||||
facets.emplace_back(Vec3crd(id, id - 1, 2));
|
||||
|
||||
return TriangleMesh(std::move(vertices), std::move(facets));
|
||||
TriangleMesh mesh(std::move(vertices), std::move(facets));
|
||||
mesh.repair();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// Generates mesh for a sphere centered about the origin, using the generated angle
|
||||
|
@ -1978,7 +1992,9 @@ TriangleMesh make_sphere(double radius, double fa)
|
|||
k2 = k2_next;
|
||||
}
|
||||
}
|
||||
return TriangleMesh(std::move(vertices), std::move(facets));
|
||||
TriangleMesh mesh(std::move(vertices), std::move(facets));
|
||||
mesh.repair();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -58,8 +58,14 @@ public:
|
|||
BoundingBoxf3 bounding_box() const;
|
||||
// Returns the bbox of this TriangleMesh transformed by the given transformation
|
||||
BoundingBoxf3 transformed_bounding_box(const Transform3d &trafo) const;
|
||||
// Return the size of the mesh in coordinates.
|
||||
Vec3d size() const { return stl.stats.size.cast<double>(); }
|
||||
/// Return the center of the related bounding box.
|
||||
Vec3d center() const { return this->bounding_box().center(); }
|
||||
// Returns the convex hull of this TriangleMesh
|
||||
TriangleMesh convex_hull_3d() const;
|
||||
// Slice this mesh at the provided Z levels and return the vector
|
||||
std::vector<ExPolygons> slice(const std::vector<double>& z);
|
||||
void reset_repair_stats();
|
||||
bool needed_repair() const;
|
||||
void require_shared_vertices();
|
||||
|
|
|
@ -245,27 +245,13 @@ std::string Preset::remove_suffix_modified(const std::string &name)
|
|||
name;
|
||||
}
|
||||
|
||||
void Preset::set_num_extruders(DynamicPrintConfig &config, unsigned int num_extruders)
|
||||
{
|
||||
const auto &defaults = FullPrintConfig::defaults();
|
||||
for (const std::string &key : Preset::nozzle_options()) {
|
||||
if (key == "default_filament_profile")
|
||||
continue;
|
||||
auto *opt = config.option(key, false);
|
||||
assert(opt != nullptr);
|
||||
assert(opt->is_vector());
|
||||
if (opt != nullptr && opt->is_vector())
|
||||
static_cast<ConfigOptionVectorBase*>(opt)->resize(num_extruders, defaults.option(key));
|
||||
}
|
||||
}
|
||||
|
||||
// Update new extruder fields at the printer profile.
|
||||
void Preset::normalize(DynamicPrintConfig &config)
|
||||
{
|
||||
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter"));
|
||||
if (nozzle_diameter != nullptr)
|
||||
// Loaded the FFF Printer settings. Verify, that all extruder dependent values have enough values.
|
||||
set_num_extruders(config, (unsigned int)nozzle_diameter->values.size());
|
||||
config.set_num_extruders((unsigned int)nozzle_diameter->values.size());
|
||||
if (config.option("filament_diameter") != nullptr) {
|
||||
// This config contains single or multiple filament presets.
|
||||
// Ensure that the filament preset vector options contain the correct number of values.
|
||||
|
@ -469,15 +455,7 @@ const std::vector<std::string>& Preset::printer_options()
|
|||
// of the nozzle_diameter vector.
|
||||
const std::vector<std::string>& Preset::nozzle_options()
|
||||
{
|
||||
// ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings
|
||||
static std::vector<std::string> s_opts {
|
||||
"nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset",
|
||||
"retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed",
|
||||
"retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe",
|
||||
"retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour",
|
||||
"default_filament_profile"
|
||||
};
|
||||
return s_opts;
|
||||
return print_config_def.extruder_option_keys();
|
||||
}
|
||||
|
||||
const std::vector<std::string>& Preset::sla_print_options()
|
||||
|
|
|
@ -202,7 +202,7 @@ public:
|
|||
void set_visible_from_appconfig(const AppConfig &app_config);
|
||||
|
||||
// Resize the extruder specific fields, initialize them with the content of the 1st extruder.
|
||||
void set_num_extruders(unsigned int n) { set_num_extruders(this->config, n); }
|
||||
void set_num_extruders(unsigned int n) { this->config.set_num_extruders(n); }
|
||||
|
||||
// Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection.
|
||||
bool operator<(const Preset &other) const { return this->name < other.name; }
|
||||
|
@ -227,8 +227,6 @@ public:
|
|||
protected:
|
||||
friend class PresetCollection;
|
||||
friend class PresetBundle;
|
||||
// Resize the extruder specific vectors ()
|
||||
static void set_num_extruders(DynamicPrintConfig &config, unsigned int n);
|
||||
static std::string remove_suffix_modified(const std::string &name);
|
||||
};
|
||||
|
||||
|
|
|
@ -10,24 +10,18 @@ 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)
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
|
||||
add_subdirectory(libnest2d)
|
||||
add_subdirectory(libslic3r)
|
||||
add_subdirectory(timeutils)
|
||||
add_subdirectory(fff_print)
|
||||
add_subdirectory(sla_print)
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
before_layer_gcode =
|
||||
between_objects_gcode =
|
||||
end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n"
|
||||
end_gcode = M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n
|
||||
extrusion_axis = E
|
||||
extrusion_multiplier = 1
|
||||
filament_cost = 0
|
||||
filament_density = 0
|
||||
filament_diameter = 3
|
||||
filament_max_volumetric_speed = 0
|
||||
gcode_comments = 0
|
||||
gcode_flavor = reprap
|
||||
layer_gcode =
|
||||
max_print_speed = 80
|
||||
max_volumetric_speed = 0
|
||||
retract_length = 2
|
||||
retract_length_toolchange = 10
|
||||
retract_lift = 1.5
|
||||
retract_lift_above = 0
|
||||
retract_lift_below = 0
|
||||
retract_restart_extra = 0
|
||||
retract_restart_extra_toolchange = 0
|
||||
retract_speed = 40
|
||||
start_filament_gcode = "; Filament gcode\n"
|
||||
start_gcode = G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n
|
||||
toolchange_gcode =
|
||||
travel_speed = 130
|
||||
use_firmware_retraction = 0
|
||||
use_relative_e_distances = 0
|
||||
use_volumetric_e = 0
|
|
@ -1,5 +1,8 @@
|
|||
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}: ")
|
||||
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "--durations yes")
|
15
tests/fff_print/CMakeLists.txt
Normal file
15
tests/fff_print/CMakeLists.txt
Normal file
|
@ -0,0 +1,15 @@
|
|||
get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
add_executable(${_TEST_NAME}_tests
|
||||
${_TEST_NAME}_tests.cpp
|
||||
test_data.cpp
|
||||
test_data.hpp
|
||||
test_flow.cpp
|
||||
test_gcodewriter.cpp
|
||||
test_skirt_brim.cpp
|
||||
test_trianglemesh.cpp
|
||||
)
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
|
||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||
|
||||
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "--durations yes")
|
4
tests/fff_print/fff_print_tests.cpp
Normal file
4
tests/fff_print/fff_print_tests.cpp
Normal file
|
@ -0,0 +1,4 @@
|
|||
#define CATCH_CONFIG_MAIN
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
327
tests/fff_print/test_data.cpp
Normal file
327
tests/fff_print/test_data.cpp
Normal file
File diff suppressed because one or more lines are too long
77
tests/fff_print/test_data.hpp
Normal file
77
tests/fff_print/test_data.hpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
#ifndef SLIC3R_TEST_DATA_HPP
|
||||
#define SLIC3R_TEST_DATA_HPP
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Slic3r { namespace Test {
|
||||
|
||||
constexpr double MM_PER_MIN = 60.0;
|
||||
|
||||
/// Enumeration of test meshes
|
||||
enum class TestMesh {
|
||||
A,
|
||||
L,
|
||||
V,
|
||||
_40x10,
|
||||
cube_20x20x20,
|
||||
sphere_50mm,
|
||||
bridge,
|
||||
bridge_with_hole,
|
||||
cube_with_concave_hole,
|
||||
cube_with_hole,
|
||||
gt2_teeth,
|
||||
ipadstand,
|
||||
overhang,
|
||||
pyramid,
|
||||
sloping_hole,
|
||||
slopy_cube,
|
||||
small_dorito,
|
||||
step,
|
||||
two_hollow_squares
|
||||
};
|
||||
|
||||
// Neccessary for <c++17
|
||||
struct TestMeshHash {
|
||||
std::size_t operator()(TestMesh tm) const {
|
||||
return static_cast<std::size_t>(tm);
|
||||
}
|
||||
};
|
||||
|
||||
/// Mesh enumeration to name mapping
|
||||
extern const std::unordered_map<TestMesh, const char*, TestMeshHash> mesh_names;
|
||||
|
||||
/// Port of Slic3r::Test::mesh
|
||||
/// Basic cubes/boxes should call TriangleMesh::make_cube() directly and rescale/translate it
|
||||
TriangleMesh mesh(TestMesh m);
|
||||
|
||||
TriangleMesh mesh(TestMesh m, Vec3d translate, Vec3d scale = Vec3d(1.0, 1.0, 1.0));
|
||||
TriangleMesh mesh(TestMesh m, Vec3d translate, double scale = 1.0);
|
||||
|
||||
/// Templated function to see if two values are equivalent (+/- epsilon)
|
||||
template <typename T>
|
||||
bool _equiv(const T& a, const T& b) { return std::abs(a - b) < EPSILON; }
|
||||
|
||||
template <typename T>
|
||||
bool _equiv(const T& a, const T& b, double epsilon) { return abs(a - b) < epsilon; }
|
||||
|
||||
//Slic3r::Model model(const std::string& model_name, TestMesh m, Vec3d translate = Vec3d(0,0,0), Vec3d scale = Vec3d(1.0,1.0,1.0));
|
||||
//Slic3r::Model model(const std::string& model_name, TestMesh m, Vec3d translate = Vec3d(0,0,0), double scale = 1.0);
|
||||
|
||||
Slic3r::Model model(const std::string& model_name, TriangleMesh&& _mesh);
|
||||
|
||||
std::shared_ptr<Print> init_print(std::initializer_list<TestMesh> meshes, Slic3r::Model& model, std::shared_ptr<Slic3r::DynamicPrintConfig> _config = std::shared_ptr<Slic3r::DynamicPrintConfig>(Slic3r::DynamicPrintConfig::new_from_defaults()), bool comments = false);
|
||||
std::shared_ptr<Print> init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Model& model, std::shared_ptr<Slic3r::DynamicPrintConfig> _config = std::shared_ptr<Slic3r::DynamicPrintConfig>(Slic3r::DynamicPrintConfig::new_from_defaults()), bool comments = false);
|
||||
|
||||
std::string gcode(std::shared_ptr<Print> print);
|
||||
|
||||
} } // namespace Slic3r::Test
|
||||
|
||||
|
||||
#endif // SLIC3R_TEST_DATA_HPP
|
203
tests/fff_print/test_flow.cpp
Normal file
203
tests/fff_print/test_flow.cpp
Normal file
|
@ -0,0 +1,203 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
|
||||
#include "test_data.hpp" // get access to init_print, etc
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
#include "libslic3r/Flow.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
using namespace Slic3r::Test;
|
||||
using namespace Slic3r;
|
||||
|
||||
SCENARIO("Extrusion width specifics", "[!mayfail]") {
|
||||
GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer and a 20mm cube mesh") {
|
||||
// this is a sharedptr
|
||||
std::shared_ptr<DynamicPrintConfig> config(Slic3r::DynamicPrintConfig::new_from_defaults());
|
||||
config->opt_int("skirts") = 1;
|
||||
config->opt_float("brim_width") = 2.;
|
||||
config->opt_int("perimeters") = 3;
|
||||
config->set_deserialize("fill_density", "40%");
|
||||
config->set_deserialize("first_layer_height", "100%");
|
||||
|
||||
WHEN("first layer width set to 2mm") {
|
||||
Slic3r::Model model;
|
||||
config->set_deserialize("first_layer_extrusion_width", "2");
|
||||
std::shared_ptr<Print> print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
|
||||
|
||||
std::vector<double> E_per_mm_bottom;
|
||||
std::string gcode = Test::gcode(print);
|
||||
Slic3r::GCodeReader parser;
|
||||
const double layer_height = config->opt_float("layer_height");
|
||||
parser.parse_buffer(gcode, [&E_per_mm_bottom, layer_height] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
|
||||
{
|
||||
if (self.z() == Approx(layer_height).margin(0.01)) { // only consider first layer
|
||||
if (line.extruding(self) && line.dist_XY(self) > 0) {
|
||||
E_per_mm_bottom.emplace_back(line.dist_E(self) / line.dist_XY(self));
|
||||
}
|
||||
}
|
||||
});
|
||||
THEN(" First layer width applies to everything on first layer.") {
|
||||
bool pass = false;
|
||||
double avg_E = std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast<double>(E_per_mm_bottom.size());
|
||||
|
||||
pass = (std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [avg_E] (const double& v) { return v == Approx(avg_E); }) == 0);
|
||||
REQUIRE(pass == true);
|
||||
REQUIRE(E_per_mm_bottom.size() > 0); // make sure it actually passed because of extrusion
|
||||
}
|
||||
THEN(" First layer width does not apply to upper layer.") {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// needs gcode export
|
||||
SCENARIO(" Bridge flow specifics.", "[!mayfail]") {
|
||||
GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio and an overhang mesh.") {
|
||||
WHEN("bridge_flow_ratio is set to 1.0") {
|
||||
THEN("Output flow is as expected.") {
|
||||
}
|
||||
}
|
||||
WHEN("bridge_flow_ratio is set to 0.5") {
|
||||
THEN("Output flow is as expected.") {
|
||||
}
|
||||
}
|
||||
WHEN("bridge_flow_ratio is set to 2.0") {
|
||||
THEN("Output flow is as expected.") {
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio, fixed extrusion width of 0.4mm and an overhang mesh.") {
|
||||
WHEN("bridge_flow_ratio is set to 1.0") {
|
||||
THEN("Output flow is as expected.") {
|
||||
}
|
||||
}
|
||||
WHEN("bridge_flow_ratio is set to 0.5") {
|
||||
THEN("Output flow is as expected.") {
|
||||
}
|
||||
}
|
||||
WHEN("bridge_flow_ratio is set to 2.0") {
|
||||
THEN("Output flow is as expected.") {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test the expected behavior for auto-width,
|
||||
/// spacing, etc
|
||||
SCENARIO("Flow: Flow math for non-bridges", "[!mayfail]") {
|
||||
GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") {
|
||||
ConfigOptionFloatOrPercent width(1.0, false);
|
||||
float spacing {0.4};
|
||||
float nozzle_diameter {0.4};
|
||||
float bridge_flow {1.0};
|
||||
float layer_height {0.5};
|
||||
|
||||
// Spacing for non-bridges is has some overlap
|
||||
THEN("External perimeter flow has spacing fixed to 1.1*nozzle_diameter") {
|
||||
auto flow = Flow::new_from_config_width(frExternalPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, 0.0f);
|
||||
REQUIRE(flow.spacing() == Approx((1.1*nozzle_diameter) - layer_height * (1.0 - PI / 4.0)));
|
||||
}
|
||||
|
||||
THEN("Internal perimeter flow has spacing of 1.05 (minimum)") {
|
||||
auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, 0.0f);
|
||||
REQUIRE(flow.spacing() == Approx((1.05*nozzle_diameter) - layer_height * (1.0 - PI / 4.0)));
|
||||
}
|
||||
THEN("Spacing for supplied width is 0.8927f") {
|
||||
auto flow = Flow::new_from_config_width(frExternalPerimeter, width, nozzle_diameter, layer_height, 0.0f);
|
||||
REQUIRE(flow.spacing() == Approx(width.value - layer_height * (1.0 - PI / 4.0)));
|
||||
flow = Flow::new_from_config_width(frPerimeter, width, nozzle_diameter, layer_height, 0.0f);
|
||||
REQUIRE(flow.spacing() == Approx(width.value - layer_height * (1.0 - PI / 4.0)));
|
||||
}
|
||||
}
|
||||
/// Check the min/max
|
||||
GIVEN("Nozzle Diameter of 0.25") {
|
||||
float spacing {0.4};
|
||||
float nozzle_diameter {0.25};
|
||||
float bridge_flow {0.0};
|
||||
float layer_height {0.5};
|
||||
WHEN("layer height is set to 0.2") {
|
||||
layer_height = 0.15f;
|
||||
THEN("Max width is set.") {
|
||||
auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, 0.0f);
|
||||
REQUIRE(flow.width == Approx(1.4*nozzle_diameter));
|
||||
}
|
||||
}
|
||||
WHEN("Layer height is set to 0.2") {
|
||||
layer_height = 0.3f;
|
||||
THEN("Min width is set.") {
|
||||
auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, 0.0f);
|
||||
REQUIRE(flow.width == Approx(1.05*nozzle_diameter));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
/// Check for an edge case in the maths where the spacing could be 0; original
|
||||
/// math is 0.99. Slic3r issue #4654
|
||||
GIVEN("Input spacing of 0.414159 and a total width of 2") {
|
||||
double in_spacing = 0.414159;
|
||||
double total_width = 2.0;
|
||||
auto flow = Flow::new_from_spacing(1.0, 0.4, 0.3, false);
|
||||
WHEN("solid_spacing() is called") {
|
||||
double result = flow.solid_spacing(total_width, in_spacing);
|
||||
THEN("Yielded spacing is greater than 0") {
|
||||
REQUIRE(result > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
/// Spacing, width calculation for bridge extrusions
|
||||
SCENARIO("Flow: Flow math for bridges", "[!mayfail]") {
|
||||
GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") {
|
||||
auto width = ConfigOptionFloatOrPercent(1.0, false);
|
||||
double spacing = 0.4;
|
||||
double nozzle_diameter = 0.4;
|
||||
double bridge_flow = 1.0;
|
||||
double layer_height = 0.5;
|
||||
WHEN("Flow role is frExternalPerimeter") {
|
||||
auto flow = Flow::new_from_config_width(frExternalPerimeter, width, nozzle_diameter, layer_height, bridge_flow);
|
||||
THEN("Bridge width is same as nozzle diameter") {
|
||||
REQUIRE(flow.width == Approx(nozzle_diameter));
|
||||
}
|
||||
THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") {
|
||||
REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING));
|
||||
}
|
||||
}
|
||||
WHEN("Flow role is frInfill") {
|
||||
auto flow = Flow::new_from_config_width(frInfill, width, nozzle_diameter, layer_height, bridge_flow);
|
||||
THEN("Bridge width is same as nozzle diameter") {
|
||||
REQUIRE(flow.width == Approx(nozzle_diameter));
|
||||
}
|
||||
THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") {
|
||||
REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING));
|
||||
}
|
||||
}
|
||||
WHEN("Flow role is frPerimeter") {
|
||||
auto flow = Flow::new_from_config_width(frPerimeter, width, nozzle_diameter, layer_height, bridge_flow);
|
||||
THEN("Bridge width is same as nozzle diameter") {
|
||||
REQUIRE(flow.width == Approx(nozzle_diameter));
|
||||
}
|
||||
THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") {
|
||||
REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING));
|
||||
}
|
||||
}
|
||||
WHEN("Flow role is frSupportMaterial") {
|
||||
auto flow = Flow::new_from_config_width(frSupportMaterial, width, nozzle_diameter, layer_height, bridge_flow);
|
||||
THEN("Bridge width is same as nozzle diameter") {
|
||||
REQUIRE(flow.width == Approx(nozzle_diameter));
|
||||
}
|
||||
THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") {
|
||||
REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
124
tests/fff_print/test_gcodewriter.cpp
Normal file
124
tests/fff_print/test_gcodewriter.cpp
Normal file
|
@ -0,0 +1,124 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "libslic3r/GCodeWriter.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
SCENARIO("lift() and unlift() behavior with large values of Z", "[!shouldfail]") {
|
||||
GIVEN("A config from a file and a single extruder.") {
|
||||
GCodeWriter writer;
|
||||
GCodeConfig &config = writer.config;
|
||||
config.load(std::string(TEST_DATA_DIR) + "/fff_print_tests/test_gcodewriter/config_lift_unlift.ini");
|
||||
|
||||
std::vector<unsigned int> extruder_ids {0};
|
||||
writer.set_extruders(extruder_ids);
|
||||
writer.set_extruder(0);
|
||||
|
||||
WHEN("Z is set to 9007199254740992") {
|
||||
double trouble_Z = 9007199254740992;
|
||||
writer.travel_to_z(trouble_Z);
|
||||
AND_WHEN("GcodeWriter::Lift() is called") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
||||
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
|
||||
AND_WHEN("GCodeWriter::Unlift() is called") {
|
||||
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
||||
THEN("GCodeWriter::Lift() emits gcode.") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("lift() is not ignored after unlift() at normal values of Z") {
|
||||
GIVEN("A config from a file and a single extruder.") {
|
||||
GCodeWriter writer;
|
||||
GCodeConfig &config = writer.config;
|
||||
config.load(std::string(TEST_DATA_DIR) + "/fff_print_tests/test_gcodewriter/config_lift_unlift.ini");
|
||||
|
||||
std::vector<unsigned int> extruder_ids {0};
|
||||
writer.set_extruders(extruder_ids);
|
||||
writer.set_extruder(0);
|
||||
|
||||
WHEN("Z is set to 203") {
|
||||
double trouble_Z = 203;
|
||||
writer.travel_to_z(trouble_Z);
|
||||
AND_WHEN("GcodeWriter::Lift() is called") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
||||
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
|
||||
AND_WHEN("GCodeWriter::Unlift() is called") {
|
||||
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
||||
THEN("GCodeWriter::Lift() emits gcode.") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WHEN("Z is set to 500003") {
|
||||
double trouble_Z = 500003;
|
||||
writer.travel_to_z(trouble_Z);
|
||||
AND_WHEN("GcodeWriter::Lift() is called") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
||||
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
|
||||
AND_WHEN("GCodeWriter::Unlift() is called") {
|
||||
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
||||
THEN("GCodeWriter::Lift() emits gcode.") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WHEN("Z is set to 10.3") {
|
||||
double trouble_Z = 10.3;
|
||||
writer.travel_to_z(trouble_Z);
|
||||
AND_WHEN("GcodeWriter::Lift() is called") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
||||
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
|
||||
AND_WHEN("GCodeWriter::Unlift() is called") {
|
||||
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
||||
THEN("GCodeWriter::Lift() emits gcode.") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("set_speed emits values with fixed-point output.") {
|
||||
|
||||
GIVEN("GCodeWriter instance") {
|
||||
GCodeWriter writer;
|
||||
WHEN("set_speed is called to set speed to 99999.123") {
|
||||
THEN("Output string is G1 F99999.123") {
|
||||
REQUIRE_THAT(writer.set_speed(99999.123), Catch::Equals("G1 F99999.123\n"));
|
||||
}
|
||||
}
|
||||
WHEN("set_speed is called to set speed to 1") {
|
||||
THEN("Output string is G1 F1.000") {
|
||||
REQUIRE_THAT(writer.set_speed(1.0), Catch::Equals("G1 F1.000\n"));
|
||||
}
|
||||
}
|
||||
WHEN("set_speed is called to set speed to 203.200022") {
|
||||
THEN("Output string is G1 F203.200") {
|
||||
REQUIRE_THAT(writer.set_speed(203.200022), Catch::Equals("G1 F203.200\n"));
|
||||
}
|
||||
}
|
||||
WHEN("set_speed is called to set speed to 203.200522") {
|
||||
THEN("Output string is G1 F203.201") {
|
||||
REQUIRE_THAT(writer.set_speed(203.200522), Catch::Equals("G1 F203.201\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
263
tests/fff_print/test_skirt_brim.cpp
Normal file
263
tests/fff_print/test_skirt_brim.cpp
Normal file
|
@ -0,0 +1,263 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "test_data.hpp" // get access to init_print, etc
|
||||
|
||||
using namespace Slic3r::Test;
|
||||
using namespace Slic3r;
|
||||
|
||||
/// Helper method to find the tool used for the brim (always the first extrusion)
|
||||
int get_brim_tool(std::string &gcode, Slic3r::GCodeReader& parser) {
|
||||
int brim_tool = -1;
|
||||
int tool = -1;
|
||||
|
||||
parser.parse_buffer(gcode, [&tool, &brim_tool] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
|
||||
{
|
||||
// if the command is a T command, set the the current tool
|
||||
if (boost::starts_with(line.cmd(), "T")) {
|
||||
tool = atoi(line.cmd().data() + 1);
|
||||
} else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0 && brim_tool < 0) {
|
||||
brim_tool = tool;
|
||||
}
|
||||
});
|
||||
|
||||
return brim_tool;
|
||||
}
|
||||
|
||||
TEST_CASE("Skirt height is honored") {
|
||||
std::shared_ptr<Slic3r::DynamicPrintConfig> config(Slic3r::DynamicPrintConfig::new_from_defaults());
|
||||
config->opt_int("skirts") = 1;
|
||||
config->opt_int("skirt_height") = 5;
|
||||
config->opt_int("perimeters") = 0;
|
||||
config->opt_float("support_material_speed") = 99;
|
||||
|
||||
// avoid altering speeds unexpectedly
|
||||
config->set_deserialize("cooling", "0");
|
||||
config->set_deserialize("first_layer_speed", "100%");
|
||||
double support_speed = config->opt<Slic3r::ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
|
||||
|
||||
std::map<double, bool> layers_with_skirt;
|
||||
std::string gcode;
|
||||
GCodeReader parser;
|
||||
Slic3r::Model model;
|
||||
|
||||
SECTION("printing a single object") {
|
||||
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
|
||||
gcode = Slic3r::Test::gcode(print);
|
||||
}
|
||||
|
||||
SECTION("printing multiple objects") {
|
||||
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20, TestMesh::cube_20x20x20}, model, config);
|
||||
gcode = Slic3r::Test::gcode(print);
|
||||
}
|
||||
parser.parse_buffer(gcode, [&layers_with_skirt, &support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
|
||||
{
|
||||
if (line.extruding(self) && self.f() == Approx(support_speed)) {
|
||||
layers_with_skirt[self.z()] = 1;
|
||||
}
|
||||
});
|
||||
|
||||
REQUIRE(layers_with_skirt.size() == (size_t)config->opt_int("skirt_height"));
|
||||
}
|
||||
|
||||
SCENARIO("Original Slic3r Skirt/Brim tests", "[!mayfail]") {
|
||||
Slic3r::GCodeReader parser;
|
||||
Slic3r::Model model;
|
||||
std::string gcode;
|
||||
GIVEN("A default configuration") {
|
||||
std::shared_ptr<Slic3r::DynamicPrintConfig> config(Slic3r::DynamicPrintConfig::new_from_defaults());
|
||||
config->set_num_extruders(4);
|
||||
config->opt_float("support_material_speed") = 99;
|
||||
config->set_deserialize("first_layer_height", "0.3");
|
||||
config->set_deserialize("gcode_comments", "1");
|
||||
|
||||
// avoid altering speeds unexpectedly
|
||||
config->set_deserialize("cooling", "0");
|
||||
config->set_deserialize("first_layer_speed", "100%");
|
||||
// remove noise from top/solid layers
|
||||
config->opt_int("top_solid_layers") = 0;
|
||||
config->opt_int("bottom_solid_layers") = 1;
|
||||
|
||||
WHEN("Brim width is set to 5") {
|
||||
config->opt_int("perimeters") = 0;
|
||||
config->opt_int("skirts") = 0;
|
||||
config->opt_float("brim_width") = 5;
|
||||
THEN("Brim is generated") {
|
||||
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
|
||||
gcode = Slic3r::Test::gcode(print);
|
||||
bool brim_generated = false;
|
||||
double support_speed = config->opt<Slic3r::ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
|
||||
parser.parse_buffer(gcode, [&brim_generated, support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
|
||||
{
|
||||
if (self.z() == Approx(0.3) || line.new_Z(self) == Approx(0.3)) {
|
||||
if (line.extruding(self) && self.f() == Approx(support_speed)) {
|
||||
brim_generated = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
REQUIRE(brim_generated);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("Skirt area is smaller than the brim") {
|
||||
config->opt_int("skirts") = 1;
|
||||
config->opt_float("brim_width") = 10;
|
||||
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
|
||||
THEN("Gcode generates") {
|
||||
REQUIRE(! Slic3r::Test::gcode(print).empty());
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("Skirt height is 0 and skirts > 0") {
|
||||
config->opt_int("skirts") = 2;
|
||||
config->opt_int("skirt_height") = 0;
|
||||
|
||||
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
|
||||
THEN("Gcode generates") {
|
||||
REQUIRE(! Slic3r::Test::gcode(print).empty());
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("Perimeter extruder = 2 and support extruders = 3") {
|
||||
config->opt_int("skirts") = 0;
|
||||
config->opt_float("brim_width") = 5;
|
||||
config->opt_int("perimeter_extruder") = 2;
|
||||
config->opt_int("support_material_extruder") = 3;
|
||||
THEN("Brim is printed with the extruder used for the perimeters of first object") {
|
||||
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
|
||||
gcode = Slic3r::Test::gcode(print);
|
||||
int tool = get_brim_tool(gcode, parser);
|
||||
REQUIRE(tool == config->opt_int("perimeter_extruder") - 1);
|
||||
}
|
||||
}
|
||||
WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") {
|
||||
config->opt_int("skirts") = 0;
|
||||
config->opt_float("brim_width") = 5;
|
||||
config->opt_int("perimeter_extruder") = 2;
|
||||
config->opt_int("support_material_extruder") = 3;
|
||||
config->opt_int("raft_layers") = 1;
|
||||
THEN("brim is printed with same extruder as skirt") {
|
||||
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
|
||||
gcode = Slic3r::Test::gcode(print);
|
||||
int tool = get_brim_tool(gcode, parser);
|
||||
REQUIRE(tool == config->opt_int("support_material_extruder") - 1);
|
||||
}
|
||||
}
|
||||
WHEN("brim width to 1 with layer_width of 0.5") {
|
||||
config->opt_int("skirts") = 0;
|
||||
config->set_deserialize("first_layer_extrusion_width", "0.5");
|
||||
config->opt_float("brim_width") = 1;
|
||||
|
||||
THEN("2 brim lines") {
|
||||
Slic3r::Model model;
|
||||
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
|
||||
print->process();
|
||||
REQUIRE(print->brim().entities.size() == 2);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
WHEN("brim ears on a square") {
|
||||
config->opt_int("skirts") = 0);
|
||||
config->set_deserialize("first_layer_extrusion_width", "0.5");
|
||||
config->opt_float("brim_width") = 1;
|
||||
config->set("brim_ears", true);
|
||||
config->set("brim_ears_max_angle", 91);
|
||||
|
||||
Slic3r::Model model;
|
||||
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
|
||||
print->process();
|
||||
|
||||
THEN("Four brim ears") {
|
||||
REQUIRE(print->brim.size() == 4);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("brim ears on a square but with a too small max angle") {
|
||||
config->set("skirts", 0);
|
||||
config->set("first_layer_extrusion_width", 0.5);
|
||||
config->set("brim_width", 1);
|
||||
config->set("brim_ears", true);
|
||||
config->set("brim_ears_max_angle", 89);
|
||||
|
||||
THEN("no brim") {
|
||||
Slic3r::Model model;
|
||||
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
|
||||
print->process();
|
||||
REQUIRE(print->brim.size() == 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
WHEN("Object is plated with overhang support and a brim") {
|
||||
config->opt_float("layer_height") = 0.4;
|
||||
config->set_deserialize("first_layer_height", "0.4");
|
||||
config->opt_int("skirts") = 1;
|
||||
config->opt_float("skirt_distance") = 0;
|
||||
config->opt_float("support_material_speed") = 99;
|
||||
config->opt_int("perimeter_extruder") = 1;
|
||||
config->opt_int("support_material_extruder") = 2;
|
||||
config->opt_int("infill_extruder") = 3; // ensure that a tool command gets emitted.
|
||||
config->set_deserialize("cooling", "0"); // to prevent speeds to be altered
|
||||
config->set_deserialize("first_layer_speed", "100%"); // to prevent speeds to be altered
|
||||
|
||||
Slic3r::Model model;
|
||||
auto print = Slic3r::Test::init_print({TestMesh::overhang}, model, config);
|
||||
print->process();
|
||||
|
||||
// config->set("support_material", true); // to prevent speeds to be altered
|
||||
|
||||
THEN("skirt length is large enough to contain object with support") {
|
||||
CHECK(config->opt_bool("support_material")); // test is not valid if support material is off
|
||||
double skirt_length = 0.0;
|
||||
Points extrusion_points;
|
||||
int tool = -1;
|
||||
|
||||
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
|
||||
std::string gcode = Slic3r::Test::gcode(print);
|
||||
|
||||
double support_speed = config->opt<ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
|
||||
parser.parse_buffer(gcode, [config, &extrusion_points, &tool, &skirt_length, support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
|
||||
{
|
||||
// std::cerr << line.cmd() << "\n";
|
||||
if (boost::starts_with(line.cmd(), "T")) {
|
||||
tool = atoi(line.cmd().data() + 1);
|
||||
} else if (self.z() == Approx(config->opt<ConfigOptionFloat>("first_layer_height")->value)) {
|
||||
// on first layer
|
||||
if (line.extruding(self) && line.dist_XY(self) > 0) {
|
||||
float speed = ( self.f() > 0 ? self.f() : line.new_F(self));
|
||||
// std::cerr << "Tool " << tool << "\n";
|
||||
if (speed == Approx(support_speed) && tool == config->opt_int("perimeter_extruder") - 1) {
|
||||
// Skirt uses first material extruder, support material speed.
|
||||
skirt_length += line.dist_XY(self);
|
||||
} else {
|
||||
extrusion_points.push_back(Slic3r::Point::new_scale(line.new_X(self), line.new_Y(self)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self.z() == Approx(0.3) || line.new_Z(self) == Approx(0.3)) {
|
||||
if (line.extruding(self) && self.f() == Approx(support_speed)) {
|
||||
}
|
||||
}
|
||||
});
|
||||
Slic3r::Polygon convex_hull = Slic3r::Geometry::convex_hull(extrusion_points);
|
||||
double hull_perimeter = unscale<double>(convex_hull.split_at_first_point().length());
|
||||
REQUIRE(skirt_length > hull_perimeter);
|
||||
}
|
||||
}
|
||||
WHEN("Large minimum skirt length is used.") {
|
||||
config->opt_float("min_skirt_length") = 20;
|
||||
Slic3r::Model model;
|
||||
auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config);
|
||||
THEN("Gcode generation doesn't crash") {
|
||||
REQUIRE(! Slic3r::Test::gcode(print).empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
433
tests/fff_print/test_trianglemesh.cpp
Normal file
433
tests/fff_print/test_trianglemesh.cpp
Normal file
|
@ -0,0 +1,433 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <future>
|
||||
#include <chrono>
|
||||
|
||||
//#include "test_options.hpp"
|
||||
#include "test_data.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace std;
|
||||
|
||||
SCENARIO( "TriangleMesh: Basic mesh statistics") {
|
||||
GIVEN( "A 20mm cube, built from constexpr std::array" ) {
|
||||
std::vector<Vec3d> vertices { Vec3d(20,20,0), Vec3d(20,0,0), Vec3d(0,0,0), Vec3d(0,20,0), Vec3d(20,20,20), Vec3d(0,20,20), Vec3d(0,0,20), Vec3d(20,0,20) };
|
||||
std::vector<Vec3crd> facets { Vec3crd(0,1,2), Vec3crd(0,2,3), Vec3crd(4,5,6), Vec3crd(4,6,7), Vec3crd(0,4,7), Vec3crd(0,7,1), Vec3crd(1,7,6), Vec3crd(1,6,2), Vec3crd(2,6,5), Vec3crd(2,5,3), Vec3crd(4,0,3), Vec3crd(4,3,5) };
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
|
||||
THEN( "Volume is appropriate for 20mm square cube.") {
|
||||
REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2);
|
||||
}
|
||||
|
||||
THEN( "Vertices array matches input.") {
|
||||
for (size_t i = 0U; i < cube.its.vertices.size(); i++) {
|
||||
REQUIRE(cube.its.vertices.at(i) == vertices.at(i).cast<float>());
|
||||
}
|
||||
for (size_t i = 0U; i < vertices.size(); i++) {
|
||||
REQUIRE(vertices.at(i).cast<float>() == cube.its.vertices.at(i));
|
||||
}
|
||||
}
|
||||
THEN( "Vertex count matches vertex array size.") {
|
||||
REQUIRE(cube.facets_count() == facets.size());
|
||||
}
|
||||
|
||||
THEN( "Facet array matches input.") {
|
||||
for (size_t i = 0U; i < cube.its.indices.size(); i++) {
|
||||
REQUIRE(cube.its.indices.at(i) == facets.at(i));
|
||||
}
|
||||
|
||||
for (size_t i = 0U; i < facets.size(); i++) {
|
||||
REQUIRE(facets.at(i) == cube.its.indices.at(i));
|
||||
}
|
||||
}
|
||||
THEN( "Facet count matches facet array size.") {
|
||||
REQUIRE(cube.facets_count() == facets.size());
|
||||
}
|
||||
|
||||
#if 0
|
||||
THEN( "Number of normals is equal to the number of facets.") {
|
||||
REQUIRE(cube.normals().size() == facets.size());
|
||||
}
|
||||
#endif
|
||||
|
||||
THEN( "center() returns the center of the object.") {
|
||||
REQUIRE(cube.center() == Vec3d(10.0,10.0,10.0));
|
||||
}
|
||||
|
||||
THEN( "Size of cube is (20,20,20)") {
|
||||
REQUIRE(cube.size() == Vec3d(20,20,20));
|
||||
}
|
||||
|
||||
}
|
||||
GIVEN( "A 20mm cube with one corner on the origin") {
|
||||
const std::vector<Vec3d> vertices { Vec3d(20,20,0), Vec3d(20,0,0), Vec3d(0,0,0), Vec3d(0,20,0), Vec3d(20,20,20), Vec3d(0,20,20), Vec3d(0,0,20), Vec3d(20,0,20) };
|
||||
const std::vector<Vec3crd> facets { Vec3crd(0,1,2), Vec3crd(0,2,3), Vec3crd(4,5,6), Vec3crd(4,6,7), Vec3crd(0,4,7), Vec3crd(0,7,1), Vec3crd(1,7,6), Vec3crd(1,6,2), Vec3crd(2,6,5), Vec3crd(2,5,3), Vec3crd(4,0,3), Vec3crd(4,3,5) };
|
||||
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
|
||||
THEN( "Volume is appropriate for 20mm square cube.") {
|
||||
REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2);
|
||||
}
|
||||
|
||||
THEN( "Vertices array matches input.") {
|
||||
for (size_t i = 0U; i < cube.its.vertices.size(); i++) {
|
||||
REQUIRE(cube.its.vertices.at(i) == vertices.at(i).cast<float>());
|
||||
}
|
||||
for (size_t i = 0U; i < vertices.size(); i++) {
|
||||
REQUIRE(vertices.at(i).cast<float>() == cube.its.vertices.at(i));
|
||||
}
|
||||
}
|
||||
THEN( "Vertex count matches vertex array size.") {
|
||||
REQUIRE(cube.facets_count() == facets.size());
|
||||
}
|
||||
|
||||
THEN( "Facet array matches input.") {
|
||||
for (size_t i = 0U; i < cube.its.indices.size(); i++) {
|
||||
REQUIRE(cube.its.indices.at(i) == facets.at(i));
|
||||
}
|
||||
|
||||
for (size_t i = 0U; i < facets.size(); i++) {
|
||||
REQUIRE(facets.at(i) == cube.its.indices.at(i));
|
||||
}
|
||||
}
|
||||
THEN( "Facet count matches facet array size.") {
|
||||
REQUIRE(cube.facets_count() == facets.size());
|
||||
}
|
||||
|
||||
#if 0
|
||||
THEN( "Number of normals is equal to the number of facets.") {
|
||||
REQUIRE(cube.normals().size() == facets.size());
|
||||
}
|
||||
#endif
|
||||
|
||||
THEN( "center() returns the center of the object.") {
|
||||
REQUIRE(cube.center() == Vec3d(10.0,10.0,10.0));
|
||||
}
|
||||
|
||||
THEN( "Size of cube is (20,20,20)") {
|
||||
REQUIRE(cube.size() == Vec3d(20,20,20));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO( "TriangleMesh: Transformation functions affect mesh as expected.") {
|
||||
GIVEN( "A 20mm cube with one corner on the origin") {
|
||||
const std::vector<Vec3d> vertices { Vec3d(20,20,0), Vec3d(20,0,0), Vec3d(0,0,0), Vec3d(0,20,0), Vec3d(20,20,20), Vec3d(0,20,20), Vec3d(0,0,20), Vec3d(20,0,20) };
|
||||
const std::vector<Vec3crd> facets { Vec3crd(0,1,2), Vec3crd(0,2,3), Vec3crd(4,5,6), Vec3crd(4,6,7), Vec3crd(0,4,7), Vec3crd(0,7,1), Vec3crd(1,7,6), Vec3crd(1,6,2), Vec3crd(2,6,5), Vec3crd(2,5,3), Vec3crd(4,0,3), Vec3crd(4,3,5) };
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
|
||||
WHEN( "The cube is scaled 200% uniformly") {
|
||||
cube.scale(2.0);
|
||||
THEN( "The volume is equivalent to 40x40x40 (all dimensions increased by 200%") {
|
||||
REQUIRE(abs(cube.volume() - 40.0*40.0*40.0) < 1e-2);
|
||||
}
|
||||
}
|
||||
WHEN( "The resulting cube is scaled 200% in the X direction") {
|
||||
cube.scale(Vec3d(2.0, 1, 1));
|
||||
THEN( "The volume is doubled.") {
|
||||
REQUIRE(abs(cube.volume() - 2*20.0*20.0*20.0) < 1e-2);
|
||||
}
|
||||
THEN( "The X coordinate size is 200%.") {
|
||||
REQUIRE(cube.its.vertices.at(0).x() == 40.0);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN( "The cube is scaled 25% in the X direction") {
|
||||
cube.scale(Vec3d(0.25, 1, 1));
|
||||
THEN( "The volume is 25% of the previous volume.") {
|
||||
REQUIRE(abs(cube.volume() - 0.25*20.0*20.0*20.0) < 1e-2);
|
||||
}
|
||||
THEN( "The X coordinate size is 25% from previous.") {
|
||||
REQUIRE(cube.its.vertices.at(0).x() == 5.0);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN( "The cube is rotated 45 degrees.") {
|
||||
cube.rotate_z(float(M_PI / 4.));
|
||||
THEN( "The X component of the size is sqrt(2)*20") {
|
||||
REQUIRE(abs(cube.size().x() - sqrt(2.0)*20) < 1e-2);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN( "The cube is translated (5, 10, 0) units with a Vec3f ") {
|
||||
cube.translate(Vec3f(5.0, 10.0, 0.0));
|
||||
THEN( "The first vertex is located at 25, 30, 0") {
|
||||
REQUIRE(cube.its.vertices.at(0) == Vec3f(25.0, 30.0, 0.0));
|
||||
}
|
||||
}
|
||||
|
||||
WHEN( "The cube is translated (5, 10, 0) units with 3 doubles") {
|
||||
cube.translate(5.0, 10.0, 0.0);
|
||||
THEN( "The first vertex is located at 25, 30, 0") {
|
||||
REQUIRE(cube.its.vertices.at(0) == Vec3f(25.0, 30.0, 0.0));
|
||||
}
|
||||
}
|
||||
WHEN( "The cube is translated (5, 10, 0) units and then aligned to origin") {
|
||||
cube.translate(5.0, 10.0, 0.0);
|
||||
cube.align_to_origin();
|
||||
THEN( "The third vertex is located at 0,0,0") {
|
||||
REQUIRE(cube.its.vertices.at(2) == Vec3f(0.0, 0.0, 0.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO( "TriangleMesh: slice behavior.") {
|
||||
GIVEN( "A 20mm cube with one corner on the origin") {
|
||||
const std::vector<Vec3d> vertices { Vec3d(20,20,0), Vec3d(20,0,0), Vec3d(0,0,0), Vec3d(0,20,0), Vec3d(20,20,20), Vec3d(0,20,20), Vec3d(0,0,20), Vec3d(20,0,20) };
|
||||
const std::vector<Vec3crd> facets { Vec3crd(0,1,2), Vec3crd(0,2,3), Vec3crd(4,5,6), Vec3crd(4,6,7), Vec3crd(0,4,7), Vec3crd(0,7,1), Vec3crd(1,7,6), Vec3crd(1,6,2), Vec3crd(2,6,5), Vec3crd(2,5,3), Vec3crd(4,0,3), Vec3crd(4,3,5) };
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
|
||||
WHEN("Cube is sliced with z = [0+EPSILON,2,4,8,6,8,10,12,14,16,18,20]") {
|
||||
std::vector<double> z { 0+EPSILON,2,4,8,6,8,10,12,14,16,18,20 };
|
||||
std::vector<ExPolygons> result = cube.slice(z);
|
||||
THEN( "The correct number of polygons are returned per layer.") {
|
||||
for (size_t i = 0U; i < z.size(); i++) {
|
||||
REQUIRE(result.at(i).size() == 1);
|
||||
}
|
||||
}
|
||||
THEN( "The area of the returned polygons is correct.") {
|
||||
for (size_t i = 0U; i < z.size(); i++) {
|
||||
REQUIRE(result.at(i).at(0).area() == 20.0*20/(std::pow(SCALING_FACTOR,2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN( "A STL with an irregular shape.") {
|
||||
const std::vector<Vec3d> vertices {Vec3d(0,0,0),Vec3d(0,0,20),Vec3d(0,5,0),Vec3d(0,5,20),Vec3d(50,0,0),Vec3d(50,0,20),Vec3d(15,5,0),Vec3d(35,5,0),Vec3d(15,20,0),Vec3d(50,5,0),Vec3d(35,20,0),Vec3d(15,5,10),Vec3d(50,5,20),Vec3d(35,5,10),Vec3d(35,20,10),Vec3d(15,20,10)};
|
||||
const std::vector<Vec3crd> facets {Vec3crd(0,1,2),Vec3crd(2,1,3),Vec3crd(1,0,4),Vec3crd(5,1,4),Vec3crd(0,2,4),Vec3crd(4,2,6),Vec3crd(7,6,8),Vec3crd(4,6,7),Vec3crd(9,4,7),Vec3crd(7,8,10),Vec3crd(2,3,6),Vec3crd(11,3,12),Vec3crd(7,12,9),Vec3crd(13,12,7),Vec3crd(6,3,11),Vec3crd(11,12,13),Vec3crd(3,1,5),Vec3crd(12,3,5),Vec3crd(5,4,9),Vec3crd(12,5,9),Vec3crd(13,7,10),Vec3crd(14,13,10),Vec3crd(8,15,10),Vec3crd(10,15,14),Vec3crd(6,11,8),Vec3crd(8,11,15),Vec3crd(15,11,13),Vec3crd(14,15,13)};
|
||||
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
WHEN(" a top tangent plane is sliced") {
|
||||
std::vector<ExPolygons> slices = cube.slice({5.0, 10.0});
|
||||
THEN( "its area is included") {
|
||||
REQUIRE(slices.at(0).at(0).area() > 0);
|
||||
REQUIRE(slices.at(1).at(0).area() > 0);
|
||||
}
|
||||
}
|
||||
WHEN(" a model that has been transformed is sliced") {
|
||||
cube.mirror_z();
|
||||
std::vector<ExPolygons> slices = cube.slice({-5.0, -10.0});
|
||||
THEN( "it is sliced properly (mirrored bottom plane area is included)") {
|
||||
REQUIRE(slices.at(0).at(0).area() > 0);
|
||||
REQUIRE(slices.at(1).at(0).area() > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO( "make_xxx functions produce meshes.") {
|
||||
GIVEN("make_cube() function") {
|
||||
WHEN("make_cube() is called with arguments 20,20,20") {
|
||||
TriangleMesh cube = make_cube(20,20,20);
|
||||
THEN("The resulting mesh has one and only one vertex at 0,0,0") {
|
||||
const std::vector<Vec3f> &verts = cube.its.vertices;
|
||||
REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return t.x() == 0 && t.y() == 0 && t.z() == 0; } ) == 1);
|
||||
}
|
||||
THEN("The mesh volume is 20*20*20") {
|
||||
REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2);
|
||||
}
|
||||
THEN("The resulting mesh is in the repaired state.") {
|
||||
REQUIRE(cube.repaired == true);
|
||||
}
|
||||
THEN("There are 12 facets.") {
|
||||
REQUIRE(cube.its.indices.size() == 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN("make_cylinder() function") {
|
||||
WHEN("make_cylinder() is called with arguments 10,10, PI / 3") {
|
||||
TriangleMesh cyl = make_cylinder(10, 10, PI / 243.0);
|
||||
double angle = (2*PI / floor(2*PI / (PI / 243.0)));
|
||||
THEN("The resulting mesh has one and only one vertex at 0,0,0") {
|
||||
const std::vector<Vec3f> &verts = cyl.its.vertices;
|
||||
REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return t.x() == 0 && t.y() == 0 && t.z() == 0; } ) == 1);
|
||||
}
|
||||
THEN("The resulting mesh has one and only one vertex at 0,0,10") {
|
||||
const std::vector<Vec3f> &verts = cyl.its.vertices;
|
||||
REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return t.x() == 0 && t.y() == 0 && t.z() == 10; } ) == 1);
|
||||
}
|
||||
THEN("Resulting mesh has 2 + (2*PI/angle * 2) vertices.") {
|
||||
REQUIRE(cyl.its.vertices.size() == (2 + ((2*PI/angle)*2)));
|
||||
}
|
||||
THEN("Resulting mesh has 2*PI/angle * 4 facets") {
|
||||
REQUIRE(cyl.its.indices.size() == (2*PI/angle)*4);
|
||||
}
|
||||
THEN("The resulting mesh is in the repaired state.") {
|
||||
REQUIRE(cyl.repaired == true);
|
||||
}
|
||||
THEN( "The mesh volume is approximately 10pi * 10^2") {
|
||||
REQUIRE(abs(cyl.volume() - (10.0 * M_PI * std::pow(10,2))) < 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("make_sphere() function") {
|
||||
WHEN("make_sphere() is called with arguments 10, PI / 3") {
|
||||
TriangleMesh sph = make_sphere(10, PI / 243.0);
|
||||
double angle = (2.0*PI / floor(2.0*PI / (PI / 243.0)));
|
||||
THEN("Resulting mesh has one point at 0,0,-10 and one at 0,0,10") {
|
||||
const std::vector<stl_vertex> &verts = sph.its.vertices;
|
||||
REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return is_approx(t, Vec3f(0.f, 0.f, 10.f)); } ) == 1);
|
||||
REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return is_approx(t, Vec3f(0.f, 0.f, -10.f)); } ) == 1);
|
||||
}
|
||||
THEN("The resulting mesh is in the repaired state.") {
|
||||
REQUIRE(sph.repaired == true);
|
||||
}
|
||||
THEN( "The mesh volume is approximately 4/3 * pi * 10^3") {
|
||||
REQUIRE(abs(sph.volume() - (4.0/3.0 * M_PI * std::pow(10,3))) < 1); // 1% tolerance?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO( "TriangleMesh: split functionality.") {
|
||||
GIVEN( "A 20mm cube with one corner on the origin") {
|
||||
const std::vector<Vec3d> vertices { Vec3d(20,20,0), Vec3d(20,0,0), Vec3d(0,0,0), Vec3d(0,20,0), Vec3d(20,20,20), Vec3d(0,20,20), Vec3d(0,0,20), Vec3d(20,0,20) };
|
||||
const std::vector<Vec3crd> facets { Vec3crd(0,1,2), Vec3crd(0,2,3), Vec3crd(4,5,6), Vec3crd(4,6,7), Vec3crd(0,4,7), Vec3crd(0,7,1), Vec3crd(1,7,6), Vec3crd(1,6,2), Vec3crd(2,6,5), Vec3crd(2,5,3), Vec3crd(4,0,3), Vec3crd(4,3,5) };
|
||||
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
WHEN( "The mesh is split into its component parts.") {
|
||||
std::vector<TriangleMesh*> meshes = cube.split();
|
||||
THEN(" The bounding box statistics are propagated to the split copies") {
|
||||
REQUIRE(meshes.size() == 1);
|
||||
REQUIRE((meshes.at(0)->bounding_box() == cube.bounding_box()));
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN( "Two 20mm cubes, each with one corner on the origin, merged into a single TriangleMesh") {
|
||||
const std::vector<Vec3d> vertices { Vec3d(20,20,0), Vec3d(20,0,0), Vec3d(0,0,0), Vec3d(0,20,0), Vec3d(20,20,20), Vec3d(0,20,20), Vec3d(0,0,20), Vec3d(20,0,20) };
|
||||
const std::vector<Vec3crd> facets { Vec3crd(0,1,2), Vec3crd(0,2,3), Vec3crd(4,5,6), Vec3crd(4,6,7), Vec3crd(0,4,7), Vec3crd(0,7,1), Vec3crd(1,7,6), Vec3crd(1,6,2), Vec3crd(2,6,5), Vec3crd(2,5,3), Vec3crd(4,0,3), Vec3crd(4,3,5) };
|
||||
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
TriangleMesh cube2(vertices, facets);
|
||||
cube2.repair();
|
||||
|
||||
cube.merge(cube2);
|
||||
cube.repair();
|
||||
WHEN( "The combined mesh is split") {
|
||||
std::vector<TriangleMesh*> meshes = cube.split();
|
||||
THEN( "Two meshes are in the output vector.") {
|
||||
REQUIRE(meshes.size() == 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO( "TriangleMesh: Mesh merge functions") {
|
||||
GIVEN( "Two 20mm cubes, each with one corner on the origin") {
|
||||
const std::vector<Vec3d> vertices { Vec3d(20,20,0), Vec3d(20,0,0), Vec3d(0,0,0), Vec3d(0,20,0), Vec3d(20,20,20), Vec3d(0,20,20), Vec3d(0,0,20), Vec3d(20,0,20) };
|
||||
const std::vector<Vec3crd> facets { Vec3crd(0,1,2), Vec3crd(0,2,3), Vec3crd(4,5,6), Vec3crd(4,6,7), Vec3crd(0,4,7), Vec3crd(0,7,1), Vec3crd(1,7,6), Vec3crd(1,6,2), Vec3crd(2,6,5), Vec3crd(2,5,3), Vec3crd(4,0,3), Vec3crd(4,3,5) };
|
||||
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
TriangleMesh cube2(vertices, facets);
|
||||
cube2.repair();
|
||||
|
||||
WHEN( "The two meshes are merged") {
|
||||
cube.merge(cube2);
|
||||
cube.repair();
|
||||
THEN( "There are twice as many facets in the merged mesh as the original.") {
|
||||
REQUIRE(cube.stl.stats.number_of_facets == 2 * cube2.stl.stats.number_of_facets);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO( "TriangleMeshSlicer: Cut behavior.") {
|
||||
GIVEN( "A 20mm cube with one corner on the origin") {
|
||||
const std::vector<Vec3d> vertices { Vec3d(20,20,0), Vec3d(20,0,0), Vec3d(0,0,0), Vec3d(0,20,0), Vec3d(20,20,20), Vec3d(0,20,20), Vec3d(0,0,20), Vec3d(20,0,20) };
|
||||
const std::vector<Vec3crd> facets { Vec3crd(0,1,2), Vec3crd(0,2,3), Vec3crd(4,5,6), Vec3crd(4,6,7), Vec3crd(0,4,7), Vec3crd(0,7,1), Vec3crd(1,7,6), Vec3crd(1,6,2), Vec3crd(2,6,5), Vec3crd(2,5,3), Vec3crd(4,0,3), Vec3crd(4,3,5) };
|
||||
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
WHEN( "Object is cut at the bottom") {
|
||||
TriangleMesh upper {};
|
||||
TriangleMesh lower {};
|
||||
TriangleMeshSlicer slicer(&cube);
|
||||
slicer.cut(0, &upper, &lower);
|
||||
THEN("Upper mesh has all facets except those belonging to the slicing plane.") {
|
||||
REQUIRE(upper.facets_count() == 12);
|
||||
}
|
||||
THEN("Lower mesh has no facets.") {
|
||||
REQUIRE(lower.facets_count() == 0);
|
||||
}
|
||||
}
|
||||
WHEN( "Object is cut at the center") {
|
||||
TriangleMesh upper {};
|
||||
TriangleMesh lower {};
|
||||
TriangleMeshSlicer slicer(&cube);
|
||||
slicer.cut(10, &upper, &lower);
|
||||
THEN("Upper mesh has 2 external horizontal facets, 3 facets on each side, and 6 facets on the triangulated side (2 + 12 + 6).") {
|
||||
REQUIRE(upper.facets_count() == 2+12+6);
|
||||
}
|
||||
THEN("Lower mesh has 2 external horizontal facets, 3 facets on each side, and 6 facets on the triangulated side (2 + 12 + 6).") {
|
||||
REQUIRE(lower.facets_count() == 2+12+6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef TEST_PERFORMANCE
|
||||
TEST_CASE("Regression test for issue #4486 - files take forever to slice") {
|
||||
TriangleMesh mesh;
|
||||
std::shared_ptr<Slic3r::DynamicPrintConfig> config = Slic3r::DynamicPrintConfig::new_from_defaults();
|
||||
mesh.ReadSTLFile(std::string(testfile_dir) + "test_trianglemesh/4486/100_000.stl");
|
||||
mesh.repair();
|
||||
|
||||
config->set("layer_height", 500);
|
||||
config->set("first_layer_height", 250);
|
||||
config->set("nozzle_diameter", 500);
|
||||
|
||||
Slic3r::Model model;
|
||||
auto print = Slic3r::Test::init_print({mesh}, model, config);
|
||||
|
||||
print->status_cb = [] (int ln, const std::string& msg) { Slic3r::Log::info("Print") << ln << " " << msg << "\n";};
|
||||
|
||||
std::future<void> fut = std::async([&print] () { print->process(); });
|
||||
std::chrono::milliseconds span {120000};
|
||||
bool timedout {false};
|
||||
if(fut.wait_for(span) == std::future_status::timeout) {
|
||||
timedout = true;
|
||||
}
|
||||
REQUIRE(timedout == false);
|
||||
|
||||
}
|
||||
#endif // TEST_PERFORMANCE
|
||||
|
||||
#ifdef BUILD_PROFILE
|
||||
TEST_CASE("Profile test for issue #4486 - files take forever to slice") {
|
||||
TriangleMesh mesh;
|
||||
std::shared_ptr<Slic3r::DynamicPrintConfig> config = Slic3r::DynamicPrintConfig::new_from_defaults();
|
||||
mesh.ReadSTLFile(std::string(testfile_dir) + "test_trianglemesh/4486/10_000.stl");
|
||||
mesh.repair();
|
||||
|
||||
config->set("layer_height", 500);
|
||||
config->set("first_layer_height", 250);
|
||||
config->set("nozzle_diameter", 500);
|
||||
config->set("fill_density", "5%");
|
||||
|
||||
Slic3r::Model model;
|
||||
auto print = Slic3r::Test::init_print({mesh}, model, config);
|
||||
|
||||
print->status_cb = [] (int ln, const std::string& msg) { Slic3r::Log::info("Print") << ln << " " << msg << "\n";};
|
||||
|
||||
print->process();
|
||||
|
||||
REQUIRE(true);
|
||||
|
||||
}
|
||||
#endif //BUILD_PROFILE
|
|
@ -1,6 +1,7 @@
|
|||
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 )
|
||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||
|
||||
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "--durations yes")
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
#include <fstream>
|
||||
|
||||
#include <libnest2d.h>
|
||||
|
||||
#include <libnest2d/libnest2d.hpp>
|
||||
#include "printer_parts.hpp"
|
||||
//#include <libnest2d/geometry_traits_nfp.hpp>
|
||||
#include "../tools/svgtools.hpp"
|
||||
|
@ -225,11 +226,11 @@ TEST_CASE("Area", "[Geometry]") {
|
|||
using namespace libnest2d;
|
||||
|
||||
RectangleItem rect(10, 10);
|
||||
|
||||
|
||||
REQUIRE(rect.area() == Approx(100));
|
||||
|
||||
RectangleItem rect2 = {100, 100};
|
||||
|
||||
|
||||
REQUIRE(rect2.area() == Approx(10000));
|
||||
|
||||
Item item = {
|
||||
|
@ -371,7 +372,7 @@ TEST_CASE("ArrangeRectanglesTight", "[Nesting]")
|
|||
REQUIRE(getX(bin.center()) == 105);
|
||||
REQUIRE(getY(bin.center()) == 125);
|
||||
|
||||
_Nester<BottomLeftPlacer, DJDHeuristic> arrange(bin);
|
||||
_Nester<BottomLeftPlacer, FirstFitSelection> arrange(bin);
|
||||
|
||||
arrange.execute(rects.begin(), rects.end());
|
||||
|
||||
|
@ -439,7 +440,7 @@ TEST_CASE("ArrangeRectanglesLoose", "[Nesting]")
|
|||
|
||||
Coord min_obj_distance = 5;
|
||||
|
||||
_Nester<BottomLeftPlacer, DJDHeuristic> arrange(bin, min_obj_distance);
|
||||
_Nester<BottomLeftPlacer, FirstFitSelection> arrange(bin, min_obj_distance);
|
||||
|
||||
arrange.execute(rects.begin(), rects.end());
|
||||
|
||||
|
@ -447,7 +448,7 @@ TEST_CASE("ArrangeRectanglesLoose", "[Nesting]")
|
|||
[](const Item &i1, const Item &i2) {
|
||||
return i1.binId() < i2.binId();
|
||||
});
|
||||
|
||||
|
||||
auto groups = size_t(max_group == rects.end() ? 0 : max_group->binId() + 1);
|
||||
|
||||
REQUIRE(groups == 1u);
|
||||
|
@ -615,7 +616,7 @@ TEST_CASE("EmptyItemShouldBeUntouched", "[Nesting]") {
|
|||
items.emplace_back(Item{0, 200, 0}); // Emplace zero area item
|
||||
|
||||
size_t bins = libnest2d::nest(items, bin);
|
||||
|
||||
|
||||
REQUIRE(bins == 0u);
|
||||
for (auto &itm : items) REQUIRE(itm.binId() == BIN_ID_UNSET);
|
||||
}
|
||||
|
@ -627,57 +628,57 @@ TEST_CASE("LargeItemShouldBeUntouched", "[Nesting]") {
|
|||
items.emplace_back(RectangleItem{250000001, 210000001}); // Emplace large item
|
||||
|
||||
size_t bins = libnest2d::nest(items, bin);
|
||||
|
||||
|
||||
REQUIRE(bins == 0u);
|
||||
REQUIRE(items.front().binId() == BIN_ID_UNSET);
|
||||
}
|
||||
|
||||
TEST_CASE("Items can be preloaded", "[Nesting]") {
|
||||
auto bin = Box({0, 0}, {250000000, 210000000}); // dummy bin
|
||||
|
||||
|
||||
std::vector<Item> items;
|
||||
items.reserve(2);
|
||||
|
||||
|
||||
NestConfig<> cfg;
|
||||
cfg.placer_config.alignment = NestConfig<>::Placement::Alignment::DONT_ALIGN;
|
||||
|
||||
|
||||
items.emplace_back(RectangleItem{10000000, 10000000});
|
||||
Item &fixed_rect = items.back();
|
||||
fixed_rect.translate(bin.center());
|
||||
|
||||
|
||||
items.emplace_back(RectangleItem{20000000, 20000000});
|
||||
Item &movable_rect = items.back();
|
||||
movable_rect.translate(bin.center());
|
||||
|
||||
|
||||
SECTION("Preloaded Item should be untouched") {
|
||||
fixed_rect.markAsFixedInBin(0);
|
||||
|
||||
|
||||
size_t bins = libnest2d::nest(items, bin, 0, cfg);
|
||||
|
||||
|
||||
REQUIRE(bins == 1);
|
||||
|
||||
|
||||
REQUIRE(fixed_rect.binId() == 0);
|
||||
REQUIRE(fixed_rect.translation().X == bin.center().X);
|
||||
REQUIRE(fixed_rect.translation().Y == bin.center().Y);
|
||||
|
||||
|
||||
REQUIRE(movable_rect.binId() == 0);
|
||||
REQUIRE(movable_rect.translation().X != bin.center().X);
|
||||
REQUIRE(movable_rect.translation().Y != bin.center().Y);
|
||||
REQUIRE(movable_rect.translation().Y != bin.center().Y);
|
||||
}
|
||||
|
||||
|
||||
SECTION("Preloaded Item should not affect free bins") {
|
||||
fixed_rect.markAsFixedInBin(1);
|
||||
|
||||
|
||||
size_t bins = libnest2d::nest(items, bin, 0, cfg);
|
||||
|
||||
|
||||
REQUIRE(bins == 2);
|
||||
|
||||
|
||||
REQUIRE(fixed_rect.binId() == 1);
|
||||
REQUIRE(fixed_rect.translation().X == bin.center().X);
|
||||
REQUIRE(fixed_rect.translation().Y == bin.center().Y);
|
||||
|
||||
|
||||
REQUIRE(movable_rect.binId() == 0);
|
||||
|
||||
|
||||
auto bb = movable_rect.boundingBox();
|
||||
REQUIRE(bb.center().X == bin.center().X);
|
||||
REQUIRE(bb.center().Y == bin.center().Y);
|
||||
|
@ -1013,7 +1014,7 @@ TEST_CASE("mergePileWithPolygon", "[Geometry]") {
|
|||
REQUIRE(result.size() == 1);
|
||||
|
||||
RectangleItem ref(45, 15);
|
||||
|
||||
|
||||
REQUIRE(shapelike::area(result.front()) == Approx(ref.area()));
|
||||
}
|
||||
|
||||
|
|
11
tests/libslic3r/CMakeLists.txt
Normal file
11
tests/libslic3r/CMakeLists.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
add_executable(${_TEST_NAME}_tests
|
||||
${_TEST_NAME}_tests.cpp
|
||||
test_geometry.cpp
|
||||
test_polygon.cpp
|
||||
)
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
|
||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||
|
||||
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "--durations yes")
|
15
tests/libslic3r/libslic3r_tests.cpp
Normal file
15
tests/libslic3r/libslic3r_tests.cpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#define CATCH_CONFIG_MAIN
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
namespace {
|
||||
|
||||
TEST_CASE("sort_remove_duplicates", "[utils]") {
|
||||
std::vector<int> data_src = { 3, 0, 2, 1, 15, 3, 5, 6, 3, 1, 0 };
|
||||
std::vector<int> data_dst = { 0, 1, 2, 3, 5, 6, 15 };
|
||||
Slic3r::sort_remove_duplicates(data_src);
|
||||
REQUIRE(data_src == data_dst);
|
||||
}
|
||||
|
||||
}
|
375
tests/libslic3r/test_geometry.cpp
Normal file
375
tests/libslic3r/test_geometry.cpp
Normal file
|
@ -0,0 +1,375 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/ShortestPath.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("Polygon::contains works properly", ""){
|
||||
// this test was failing on Windows (GH #1950)
|
||||
Slic3r::Polygon polygon(std::vector<Point>({
|
||||
Point(207802834,-57084522),
|
||||
Point(196528149,-37556190),
|
||||
Point(173626821,-25420928),
|
||||
Point(171285751,-21366123),
|
||||
Point(118673592,-21366123),
|
||||
Point(116332562,-25420928),
|
||||
Point(93431208,-37556191),
|
||||
Point(82156517,-57084523),
|
||||
Point(129714478,-84542120),
|
||||
Point(160244873,-84542120)
|
||||
}));
|
||||
Point point(95706562, -57294774);
|
||||
REQUIRE(polygon.contains(point));
|
||||
}
|
||||
|
||||
SCENARIO("Intersections of line segments"){
|
||||
GIVEN("Integer coordinates"){
|
||||
Line line1(Point(5,15),Point(30,15));
|
||||
Line line2(Point(10,20), Point(10,10));
|
||||
THEN("The intersection is valid"){
|
||||
Point point;
|
||||
line1.intersection(line2,&point);
|
||||
REQUIRE(Point(10,15) == point);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Scaled coordinates"){
|
||||
Line line1(Point(73.6310778185108 / 0.00001, 371.74239268924 / 0.00001), Point(73.6310778185108 / 0.00001, 501.74239268924 / 0.00001));
|
||||
Line line2(Point(75/0.00001, 437.9853/0.00001), Point(62.7484/0.00001, 440.4223/0.00001));
|
||||
THEN("There is still an intersection"){
|
||||
Point point;
|
||||
REQUIRE(line1.intersection(line2,&point));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Tests for unused methods still written in perl
|
||||
{
|
||||
my $polygon = Slic3r::Polygon->new(
|
||||
[45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800],
|
||||
[43749700, 343843000], [45422300, 352251500], [52362100, 362637800], [62748400, 369577600],
|
||||
[75000000, 372014700], [87251500, 369577600], [97637800, 362637800], [104577600, 352251500],
|
||||
[107014700, 340000000], [104577600, 327748400], [97637800, 317362100], [87251500, 310422300],
|
||||
[82789200, 309534700], [69846100, 294726100], [254081000, 294726100], [285273900, 348753500],
|
||||
[285273900, 461246400], [254081000, 515273900],
|
||||
);
|
||||
|
||||
# this points belongs to $polyline
|
||||
# note: it's actually a vertex, while we should better check an intermediate point
|
||||
my $point = Slic3r::Point->new(104577600, 327748400);
|
||||
|
||||
local $Slic3r::Geometry::epsilon = 1E-5;
|
||||
is_deeply Slic3r::Geometry::polygon_segment_having_point($polygon, $point)->pp,
|
||||
[ [107014700, 340000000], [104577600, 327748400] ],
|
||||
'polygon_segment_having_point';
|
||||
}
|
||||
{
|
||||
auto point = Point(736310778.185108, 5017423926.8924);
|
||||
auto line = Line(Point((long int) 627484000, (long int) 3695776000), Point((long int) 750000000, (long int)3720147000));
|
||||
//is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment';
|
||||
}
|
||||
|
||||
// Possible to delete
|
||||
{
|
||||
//my $p1 = [10, 10];
|
||||
//my $p2 = [10, 20];
|
||||
//my $p3 = [10, 30];
|
||||
//my $p4 = [20, 20];
|
||||
//my $p5 = [0, 20];
|
||||
|
||||
THEN("Points in a line give the correct angles"){
|
||||
//is Slic3r::Geometry::angle3points($p2, $p3, $p1), PI(), 'angle3points';
|
||||
//is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points';
|
||||
}
|
||||
THEN("Left turns give the correct angle"){
|
||||
//is Slic3r::Geometry::angle3points($p2, $p4, $p3), PI()/2, 'angle3points';
|
||||
//is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2, 'angle3points';
|
||||
}
|
||||
THEN("Right turns give the correct angle"){
|
||||
//is Slic3r::Geometry::angle3points($p2, $p3, $p4), PI()/2*3, 'angle3points';
|
||||
//is Slic3r::Geometry::angle3points($p2, $p1, $p5), PI()/2*3, 'angle3points';
|
||||
}
|
||||
//my $p1 = [30, 30];
|
||||
//my $p2 = [20, 20];
|
||||
//my $p3 = [10, 10];
|
||||
//my $p4 = [30, 10];
|
||||
|
||||
//is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points';
|
||||
//is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2*3, 'angle3points';
|
||||
//is Slic3r::Geometry::angle3points($p2, $p1, $p1), 2*PI(), 'angle3points';
|
||||
}
|
||||
|
||||
SCENARIO("polygon_is_convex works"){
|
||||
GIVEN("A square of dimension 10"){
|
||||
//my $cw_square = [ [0,0], [0,10], [10,10], [10,0] ];
|
||||
THEN("It is not convex clockwise"){
|
||||
//is polygon_is_convex($cw_square), 0, 'cw square is not convex';
|
||||
}
|
||||
THEN("It is convex counter-clockwise"){
|
||||
//is polygon_is_convex([ reverse @$cw_square ]), 1, 'ccw square is convex';
|
||||
}
|
||||
|
||||
}
|
||||
GIVEN("A concave polygon"){
|
||||
//my $convex1 = [ [0,0], [10,0], [10,10], [0,10], [0,6], [4,6], [4,4], [0,4] ];
|
||||
THEN("It is concave"){
|
||||
//is polygon_is_convex($convex1), 0, 'concave polygon';
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
TEST_CASE("Creating a polyline generates the obvious lines"){
|
||||
Slic3r::Polyline polyline;
|
||||
polyline.points = std::vector<Point>({Point(0, 0), Point(10, 0), Point(20, 0)});
|
||||
REQUIRE(polyline.lines().at(0).a == Point(0,0));
|
||||
REQUIRE(polyline.lines().at(0).b == Point(10,0));
|
||||
REQUIRE(polyline.lines().at(1).a == Point(10,0));
|
||||
REQUIRE(polyline.lines().at(1).b == Point(20,0));
|
||||
}
|
||||
|
||||
TEST_CASE("Splitting a Polygon generates a polyline correctly"){
|
||||
Slic3r::Polygon polygon(std::vector<Point>({Point(0, 0), Point(10, 0), Point(5, 5)}));
|
||||
Slic3r::Polyline split = polygon.split_at_index(1);
|
||||
REQUIRE(split.points[0]==Point(10,0));
|
||||
REQUIRE(split.points[1]==Point(5,5));
|
||||
REQUIRE(split.points[2]==Point(0,0));
|
||||
REQUIRE(split.points[3]==Point(10,0));
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Bounding boxes are scaled appropriately"){
|
||||
BoundingBox bb(std::vector<Point>({Point(0, 1), Point(10, 2), Point(20, 2)}));
|
||||
bb.scale(2);
|
||||
REQUIRE(bb.min == Point(0,2));
|
||||
REQUIRE(bb.max == Point(40,4));
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Offseting a line generates a polygon correctly"){
|
||||
Slic3r::Polyline tmp = { Point(10,10), Point(20,10) };
|
||||
Slic3r::Polygon area = offset(tmp,5).at(0);
|
||||
REQUIRE(area.area() == Slic3r::Polygon(std::vector<Point>({Point(10,5),Point(20,5),Point(20,15),Point(10,15)})).area());
|
||||
}
|
||||
|
||||
SCENARIO("Circle Fit, TaubinFit with Newton's method") {
|
||||
GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
|
||||
Vec2d expected_center(-6, 0);
|
||||
Vec2ds sample {Vec2d(6.0, 0), Vec2d(5.1961524, 3), Vec2d(3 ,5.1961524), Vec2d(0, 6.0), Vec2d(3, 5.1961524), Vec2d(-5.1961524, 3), Vec2d(-6.0, 0)};
|
||||
std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Vec2d& a) { return a + expected_center;});
|
||||
|
||||
WHEN("Circle fit is called on the entire array") {
|
||||
Vec2d result_center(0,0);
|
||||
result_center = Geometry::circle_taubin_newton(sample);
|
||||
THEN("A center point of -6,0 is returned.") {
|
||||
REQUIRE(is_approx(result_center, expected_center));
|
||||
}
|
||||
}
|
||||
WHEN("Circle fit is called on the first four points") {
|
||||
Vec2d result_center(0,0);
|
||||
result_center = Geometry::circle_taubin_newton(sample.cbegin(), sample.cbegin()+4);
|
||||
THEN("A center point of -6,0 is returned.") {
|
||||
REQUIRE(is_approx(result_center, expected_center));
|
||||
}
|
||||
}
|
||||
WHEN("Circle fit is called on the middle four points") {
|
||||
Vec2d result_center(0,0);
|
||||
result_center = Geometry::circle_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
|
||||
THEN("A center point of -6,0 is returned.") {
|
||||
REQUIRE(is_approx(result_center, expected_center));
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
|
||||
Vec2d expected_center(-3, 9);
|
||||
Vec2ds sample {Vec2d(6.0, 0), Vec2d(5.1961524, 3), Vec2d(3 ,5.1961524),
|
||||
Vec2d(0, 6.0),
|
||||
Vec2d(3, 5.1961524), Vec2d(-5.1961524, 3), Vec2d(-6.0, 0)};
|
||||
|
||||
std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Vec2d& a) { return a + expected_center;});
|
||||
|
||||
|
||||
WHEN("Circle fit is called on the entire array") {
|
||||
Vec2d result_center(0,0);
|
||||
result_center = Geometry::circle_taubin_newton(sample);
|
||||
THEN("A center point of 3,9 is returned.") {
|
||||
REQUIRE(is_approx(result_center, expected_center));
|
||||
}
|
||||
}
|
||||
WHEN("Circle fit is called on the first four points") {
|
||||
Vec2d result_center(0,0);
|
||||
result_center = Geometry::circle_taubin_newton(sample.cbegin(), sample.cbegin()+4);
|
||||
THEN("A center point of 3,9 is returned.") {
|
||||
REQUIRE(is_approx(result_center, expected_center));
|
||||
}
|
||||
}
|
||||
WHEN("Circle fit is called on the middle four points") {
|
||||
Vec2d result_center(0,0);
|
||||
result_center = Geometry::circle_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
|
||||
THEN("A center point of 3,9 is returned.") {
|
||||
REQUIRE(is_approx(result_center, expected_center));
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN("A vector of Points arranged in a half-circle with approximately the same distance R from some point") {
|
||||
Point expected_center { Point::new_scale(-3, 9)};
|
||||
Points sample {Point::new_scale(6.0, 0), Point::new_scale(5.1961524, 3), Point::new_scale(3 ,5.1961524),
|
||||
Point::new_scale(0, 6.0),
|
||||
Point::new_scale(3, 5.1961524), Point::new_scale(-5.1961524, 3), Point::new_scale(-6.0, 0)};
|
||||
|
||||
std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Point& a) { return a + expected_center;});
|
||||
|
||||
|
||||
WHEN("Circle fit is called on the entire array") {
|
||||
Point result_center(0,0);
|
||||
result_center = Geometry::circle_taubin_newton(sample);
|
||||
THEN("A center point of scaled 3,9 is returned.") {
|
||||
REQUIRE(is_approx(result_center, expected_center));
|
||||
}
|
||||
}
|
||||
WHEN("Circle fit is called on the first four points") {
|
||||
Point result_center(0,0);
|
||||
result_center = Geometry::circle_taubin_newton(sample.cbegin(), sample.cbegin()+4);
|
||||
THEN("A center point of scaled 3,9 is returned.") {
|
||||
REQUIRE(is_approx(result_center, expected_center));
|
||||
}
|
||||
}
|
||||
WHEN("Circle fit is called on the middle four points") {
|
||||
Point result_center(0,0);
|
||||
result_center = Geometry::circle_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
|
||||
THEN("A center point of scaled 3,9 is returned.") {
|
||||
REQUIRE(is_approx(result_center, expected_center));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Chained path working correctly"){
|
||||
// if chained_path() works correctly, these points should be joined with no diagonal paths
|
||||
// (thus 26 units long)
|
||||
std::vector<Point> points = {Point(26,26),Point(52,26),Point(0,26),Point(26,52),Point(26,0),Point(0,52),Point(52,52),Point(52,0)};
|
||||
std::vector<Points::size_type> indices = chain_points(points);
|
||||
for (Points::size_type i = 0; i + 1 < indices.size(); ++ i) {
|
||||
double dist = (points.at(indices.at(i)).cast<double>() - points.at(indices.at(i+1)).cast<double>()).norm();
|
||||
REQUIRE(std::abs(dist-26) <= EPSILON);
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Line distances"){
|
||||
GIVEN("A line"){
|
||||
Line line(Point(0, 0), Point(20, 0));
|
||||
THEN("Points on the line segment have 0 distance"){
|
||||
REQUIRE(line.distance_to(Point(0, 0)) == 0);
|
||||
REQUIRE(line.distance_to(Point(20, 0)) == 0);
|
||||
REQUIRE(line.distance_to(Point(10, 0)) == 0);
|
||||
|
||||
}
|
||||
THEN("Points off the line have the appropriate distance"){
|
||||
REQUIRE(line.distance_to(Point(10, 10)) == 10);
|
||||
REQUIRE(line.distance_to(Point(50, 0)) == 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Polygon convex/concave detection"){
|
||||
GIVEN(("A Square with dimension 100")){
|
||||
auto square = Slic3r::Polygon /*new_scale*/(std::vector<Point>({
|
||||
Point(100,100),
|
||||
Point(200,100),
|
||||
Point(200,200),
|
||||
Point(100,200)}));
|
||||
THEN("It has 4 convex points counterclockwise"){
|
||||
REQUIRE(square.concave_points(PI*4/3).size() == 0);
|
||||
REQUIRE(square.convex_points(PI*2/3).size() == 4);
|
||||
}
|
||||
THEN("It has 4 concave points clockwise"){
|
||||
square.make_clockwise();
|
||||
REQUIRE(square.concave_points(PI*4/3).size() == 4);
|
||||
REQUIRE(square.convex_points(PI*2/3).size() == 0);
|
||||
}
|
||||
}
|
||||
GIVEN("A Square with an extra colinearvertex"){
|
||||
auto square = Slic3r::Polygon /*new_scale*/(std::vector<Point>({
|
||||
Point(150,100),
|
||||
Point(200,100),
|
||||
Point(200,200),
|
||||
Point(100,200),
|
||||
Point(100,100)}));
|
||||
THEN("It has 4 convex points counterclockwise"){
|
||||
REQUIRE(square.concave_points(PI*4/3).size() == 0);
|
||||
REQUIRE(square.convex_points(PI*2/3).size() == 4);
|
||||
}
|
||||
}
|
||||
GIVEN("A Square with an extra collinear vertex in different order"){
|
||||
auto square = Slic3r::Polygon /*new_scale*/(std::vector<Point>({
|
||||
Point(200,200),
|
||||
Point(100,200),
|
||||
Point(100,100),
|
||||
Point(150,100),
|
||||
Point(200,100)}));
|
||||
THEN("It has 4 convex points counterclockwise"){
|
||||
REQUIRE(square.concave_points(PI*4/3).size() == 0);
|
||||
REQUIRE(square.convex_points(PI*2/3).size() == 4);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A triangle"){
|
||||
auto triangle = Slic3r::Polygon(std::vector<Point>({
|
||||
Point(16000170,26257364),
|
||||
Point(714223,461012),
|
||||
Point(31286371,461008)
|
||||
}));
|
||||
THEN("it has three convex vertices"){
|
||||
REQUIRE(triangle.concave_points(PI*4/3).size() == 0);
|
||||
REQUIRE(triangle.convex_points(PI*2/3).size() == 3);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A triangle with an extra collinear point"){
|
||||
auto triangle = Slic3r::Polygon(std::vector<Point>({
|
||||
Point(16000170,26257364),
|
||||
Point(714223,461012),
|
||||
Point(20000000,461012),
|
||||
Point(31286371,461012)
|
||||
}));
|
||||
THEN("it has three convex vertices"){
|
||||
REQUIRE(triangle.concave_points(PI*4/3).size() == 0);
|
||||
REQUIRE(triangle.convex_points(PI*2/3).size() == 3);
|
||||
}
|
||||
}
|
||||
GIVEN("A polygon with concave vertices with angles of specifically 4/3pi"){
|
||||
// Two concave vertices of this polygon have angle = PI*4/3, so this test fails
|
||||
// if epsilon is not used.
|
||||
auto polygon = Slic3r::Polygon(std::vector<Point>({
|
||||
Point(60246458,14802768),Point(64477191,12360001),
|
||||
Point(63727343,11060995),Point(64086449,10853608),
|
||||
Point(66393722,14850069),Point(66034704,15057334),
|
||||
Point(65284646,13758387),Point(61053864,16200839),
|
||||
Point(69200258,30310849),Point(62172547,42483120),
|
||||
Point(61137680,41850279),Point(67799985,30310848),
|
||||
Point(51399866,1905506),Point(38092663,1905506),
|
||||
Point(38092663,692699),Point(52100125,692699)
|
||||
}));
|
||||
THEN("the correct number of points are detected"){
|
||||
REQUIRE(polygon.concave_points(PI*4/3).size() == 6);
|
||||
REQUIRE(polygon.convex_points(PI*2/3).size() == 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Triangle Simplification does not result in less than 3 points"){
|
||||
auto triangle = Slic3r::Polygon(std::vector<Point>({
|
||||
Point(16000170,26257364), Point(714223,461012), Point(31286371,461008)
|
||||
}));
|
||||
REQUIRE(triangle.simplify(250000).at(0).points.size() == 3);
|
||||
}
|
||||
|
||||
|
44
tests/libslic3r/test_polygon.cpp
Normal file
44
tests/libslic3r/test_polygon.cpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
// This test currently only covers remove_collinear_points.
|
||||
// All remaining tests are to be ported from xs/t/06_polygon.t
|
||||
|
||||
Slic3r::Points collinear_circle({
|
||||
Slic3r::Point::new_scale(0, 0), // 3 collinear points at beginning
|
||||
Slic3r::Point::new_scale(10, 0),
|
||||
Slic3r::Point::new_scale(20, 0),
|
||||
Slic3r::Point::new_scale(30, 10),
|
||||
Slic3r::Point::new_scale(40, 20), // 2 collinear points
|
||||
Slic3r::Point::new_scale(40, 30),
|
||||
Slic3r::Point::new_scale(30, 40), // 3 collinear points
|
||||
Slic3r::Point::new_scale(20, 40),
|
||||
Slic3r::Point::new_scale(10, 40),
|
||||
Slic3r::Point::new_scale(-10, 20),
|
||||
Slic3r::Point::new_scale(-20, 10),
|
||||
Slic3r::Point::new_scale(-20, 0), // 3 collinear points at end
|
||||
Slic3r::Point::new_scale(-10, 0),
|
||||
Slic3r::Point::new_scale(-5, 0)
|
||||
});
|
||||
|
||||
SCENARIO("Remove collinear points from Polygon") {
|
||||
GIVEN("Polygon with collinear points"){
|
||||
Slic3r::Polygon p(collinear_circle);
|
||||
WHEN("collinear points are removed") {
|
||||
remove_collinear(p);
|
||||
THEN("Leading collinear points are removed") {
|
||||
REQUIRE(p.points.front() == Slic3r::Point::new_scale(20, 0));
|
||||
}
|
||||
THEN("Trailing collinear points are removed") {
|
||||
REQUIRE(p.points.back() == Slic3r::Point::new_scale(-20, 0));
|
||||
}
|
||||
THEN("Number of remaining points is correct") {
|
||||
REQUIRE(p.points.size() == 7);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
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)
|
||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||
|
||||
#catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "--durations yes")
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
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)
|
||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||
|
||||
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests "--durations yes")
|
||||
|
|
Loading…
Reference in a new issue