mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_3dconnexion
This commit is contained in:
		
						commit
						93ae170113
					
				
					 71 changed files with 4828 additions and 5978 deletions
				
			
		|  | @ -291,14 +291,14 @@ if(SLIC3R_STATIC) | |||
| endif() | ||||
| set(TBB_DEBUG 1) | ||||
| find_package(TBB REQUIRED) | ||||
| include_directories(${TBB_INCLUDE_DIRS}) | ||||
| add_definitions(${TBB_DEFINITIONS}) | ||||
| if(MSVC) | ||||
|     # Suppress implicit linking of the TBB libraries by the Visual Studio compiler. | ||||
|     add_definitions(-D__TBB_NO_IMPLICIT_LINKAGE) | ||||
| endif() | ||||
| # include_directories(${TBB_INCLUDE_DIRS}) | ||||
| # add_definitions(${TBB_DEFINITIONS}) | ||||
| # if(MSVC) | ||||
| #     # Suppress implicit linking of the TBB libraries by the Visual Studio compiler. | ||||
| #     add_definitions(-D__TBB_NO_IMPLICIT_LINKAGE) | ||||
| # endif() | ||||
| # The Intel TBB library will use the std::exception_ptr feature of C++11. | ||||
| add_definitions(-DTBB_USE_CAPTURED_EXCEPTION=0) | ||||
| # add_definitions(-DTBB_USE_CAPTURED_EXCEPTION=0) | ||||
| 
 | ||||
| find_package(CURL REQUIRED) | ||||
| include_directories(${CURL_INCLUDE_DIRS}) | ||||
|  | @ -375,6 +375,8 @@ add_custom_target(pot | |||
|     COMMENT "Generate pot file from strings in the source tree" | ||||
| ) | ||||
| 
 | ||||
| find_package(NLopt 1.4 REQUIRED) | ||||
| 
 | ||||
| # libslic3r, PrusaSlicer GUI and the PrusaSlicer executable. | ||||
| add_subdirectory(src) | ||||
| set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT PrusaSlicer_app_console) | ||||
|  |  | |||
|  | @ -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} | ||||
|     ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -668,6 +668,12 @@ void ConfigBase::null_nullables() | |||
|     } | ||||
| } | ||||
| 
 | ||||
| DynamicConfig::DynamicConfig(const ConfigBase& rhs, const t_config_option_keys& keys) | ||||
| { | ||||
| 	for (const t_config_option_key& opt_key : keys) | ||||
| 		this->options[opt_key] = std::unique_ptr<ConfigOption>(rhs.option(opt_key)->clone()); | ||||
| } | ||||
| 
 | ||||
| bool DynamicConfig::operator==(const DynamicConfig &rhs) const | ||||
| { | ||||
|     auto it1     = this->options.begin(); | ||||
|  |  | |||
|  | @ -1580,9 +1580,11 @@ class DynamicConfig : public virtual ConfigBase | |||
| { | ||||
| public: | ||||
|     DynamicConfig() {} | ||||
|     DynamicConfig(const DynamicConfig& other) { *this = other; } | ||||
|     DynamicConfig(DynamicConfig&& other) : options(std::move(other.options)) { other.options.clear(); } | ||||
|     virtual ~DynamicConfig() override { clear(); } | ||||
|     DynamicConfig(const DynamicConfig &rhs) { *this = rhs; } | ||||
|     DynamicConfig(DynamicConfig &&rhs) : options(std::move(rhs.options)) { rhs.options.clear(); } | ||||
| 	explicit DynamicConfig(const ConfigBase &rhs, const t_config_option_keys &keys); | ||||
| 	explicit DynamicConfig(const ConfigBase& rhs) : DynamicConfig(rhs, rhs.keys()) {} | ||||
| 	virtual ~DynamicConfig() override { clear(); } | ||||
| 
 | ||||
|     // Copy a content of one DynamicConfig to another DynamicConfig.
 | ||||
|     // If rhs.def() is not null, then it has to be equal to this->def(). 
 | ||||
|  |  | |||
|  | @ -18,15 +18,20 @@ class ExPolygon | |||
| { | ||||
| public: | ||||
|     ExPolygon() {} | ||||
|     ExPolygon(const ExPolygon &other) : contour(other.contour), holes(other.holes) {} | ||||
| 	ExPolygon(const ExPolygon &other) : contour(other.contour), holes(other.holes) {} | ||||
|     ExPolygon(ExPolygon &&other) : contour(std::move(other.contour)), holes(std::move(other.holes)) {} | ||||
| 	explicit ExPolygon(const Polygon &contour) : contour(contour) {} | ||||
| 	explicit ExPolygon(Polygon &&contour) : contour(std::move(contour)) {} | ||||
| 	explicit ExPolygon(const Points &contour) : contour(contour) {} | ||||
| 	explicit ExPolygon(Points &&contour) : contour(std::move(contour)) {} | ||||
| 	explicit ExPolygon(const Polygon &contour, const Polygon &hole) : contour(contour) { holes.emplace_back(hole); } | ||||
| 	explicit ExPolygon(Polygon &&contour, Polygon &&hole) : contour(std::move(contour)) { holes.emplace_back(std::move(hole)); } | ||||
| 	explicit ExPolygon(const Points &contour, const Points &hole) : contour(contour) { holes.emplace_back(hole); } | ||||
| 	explicit ExPolygon(Points &&contour, Polygon &&hole) : contour(std::move(contour)) { holes.emplace_back(std::move(hole)); } | ||||
| 
 | ||||
|     ExPolygon& operator=(const ExPolygon &other) { contour = other.contour; holes = other.holes; return *this; } | ||||
|     ExPolygon& operator=(ExPolygon &&other) { contour = std::move(other.contour); holes = std::move(other.holes); return *this; } | ||||
| 
 | ||||
|     inline explicit ExPolygon(const Polygon &p): contour(p) {} | ||||
|     inline explicit ExPolygon(Polygon &&p): contour(std::move(p)) {} | ||||
| 
 | ||||
|     Polygon contour; | ||||
|     Polygons holes; | ||||
| 
 | ||||
|  |  | |||
|  | @ -29,8 +29,6 @@ public: | |||
|     FillParams   params; | ||||
| }; | ||||
| 
 | ||||
| void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out); | ||||
| 
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| #endif // slic3r_Fill_hpp_
 | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ | |||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| class ExPolygon; | ||||
| class Surface; | ||||
| 
 | ||||
| struct FillParams | ||||
|  |  | |||
|  | @ -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", | ||||
|  | @ -2889,9 +2898,13 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va | |||
| 
 | ||||
| const PrintConfigDef print_config_def; | ||||
| 
 | ||||
| DynamicPrintConfig* DynamicPrintConfig::new_from_defaults() | ||||
| DynamicPrintConfig DynamicPrintConfig::full_print_config() | ||||
| { | ||||
| 	return DynamicPrintConfig((const PrintRegionConfig&)FullPrintConfig::defaults()); | ||||
| } | ||||
| 
 | ||||
| DynamicPrintConfig::DynamicPrintConfig(const StaticPrintConfig& rhs) : DynamicConfig(rhs, rhs.keys_ref()) | ||||
| { | ||||
|     return new_from_defaults_keys(FullPrintConfig::defaults().keys()); | ||||
| } | ||||
| 
 | ||||
| DynamicPrintConfig* DynamicPrintConfig::new_from_defaults_keys(const std::vector<std::string> &keys) | ||||
|  | @ -2938,6 +2951,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; | ||||
| }; | ||||
| 
 | ||||
|  | @ -211,6 +214,8 @@ private: | |||
| // This definition is constant.
 | ||||
| extern const PrintConfigDef print_config_def; | ||||
| 
 | ||||
| class StaticPrintConfig; | ||||
| 
 | ||||
| // Slic3r dynamic configuration, used to override the configuration
 | ||||
| // per object, per modification volume or per printing material.
 | ||||
| // The dynamic configuration is also used to store user modifications of the print global parameters,
 | ||||
|  | @ -221,9 +226,11 @@ class DynamicPrintConfig : public DynamicConfig | |||
| { | ||||
| public: | ||||
|     DynamicPrintConfig() {} | ||||
|     DynamicPrintConfig(const DynamicPrintConfig &other) : DynamicConfig(other) {} | ||||
|     DynamicPrintConfig(const DynamicPrintConfig &rhs) : DynamicConfig(rhs) {} | ||||
|     explicit DynamicPrintConfig(const StaticPrintConfig &rhs); | ||||
|     explicit DynamicPrintConfig(const ConfigBase &rhs) : DynamicConfig(rhs) {} | ||||
| 
 | ||||
|     static DynamicPrintConfig* new_from_defaults(); | ||||
|     static DynamicPrintConfig  full_print_config(); | ||||
|     static DynamicPrintConfig* new_from_defaults_keys(const std::vector<std::string> &keys); | ||||
| 
 | ||||
|     // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here.
 | ||||
|  | @ -231,6 +238,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(); | ||||
| 
 | ||||
|  | @ -257,6 +266,8 @@ public: | |||
| 
 | ||||
|     // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here.
 | ||||
|     const ConfigDef*    def() const override { return &print_config_def; } | ||||
|     // Reference to the cached list of keys.
 | ||||
| 	virtual const t_config_option_keys& keys_ref() const = 0; | ||||
| 
 | ||||
| protected: | ||||
|     // Verify whether the opt_key has not been obsoleted or renamed.
 | ||||
|  | @ -345,6 +356,7 @@ public: \ | |||
|         { return s_cache_##CLASS_NAME.optptr(opt_key, this); } \ | ||||
|     /* Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. */ \ | ||||
|     t_config_option_keys     keys() const override { return s_cache_##CLASS_NAME.keys(); } \ | ||||
|     const t_config_option_keys& keys_ref() const override { return s_cache_##CLASS_NAME.keys(); } \ | ||||
|     static const CLASS_NAME& defaults() { initialize_cache(); return s_cache_##CLASS_NAME.defaults(); } \ | ||||
| private: \ | ||||
|     static void initialize_cache() \ | ||||
|  |  | |||
|  | @ -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 | ||||
							
								
								
									
										
											BIN
										
									
								
								tests/data/test_3mf/Geräte/Büchse.3mf
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								tests/data/test_3mf/Geräte/Büchse.3mf
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -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") | ||||
							
								
								
									
										20
									
								
								tests/fff_print/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								tests/fff_print/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| 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_fill.cpp | ||||
| 	test_flow.cpp | ||||
| 	test_gcodewriter.cpp | ||||
| 	test_model.cpp | ||||
| 	test_print.cpp | ||||
| 	test_printgcode.cpp | ||||
| 	test_printobject.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, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false); | ||||
| std::shared_ptr<Print> init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Model& model, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false); | ||||
| 
 | ||||
| std::string gcode(std::shared_ptr<Print> print); | ||||
| 
 | ||||
| } } // namespace Slic3r::Test
 | ||||
| 
 | ||||
| 
 | ||||
| #endif // SLIC3R_TEST_DATA_HPP
 | ||||
							
								
								
									
										474
									
								
								tests/fff_print/test_fill.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										474
									
								
								tests/fff_print/test_fill.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,474 @@ | |||
| #include <catch2/catch.hpp> | ||||
| 
 | ||||
| #include <numeric> | ||||
| #include <sstream> | ||||
| 
 | ||||
| #include "libslic3r/ClipperUtils.hpp" | ||||
| #include "libslic3r/Fill/Fill.hpp" | ||||
| #include "libslic3r/Flow.hpp" | ||||
| #include "libslic3r/Geometry.hpp" | ||||
| #include "libslic3r/Print.hpp" | ||||
| #include "libslic3r/SVG.hpp" | ||||
| #include "libslic3r/libslic3r.h" | ||||
| 
 | ||||
| #include "test_data.hpp" | ||||
| 
 | ||||
| using namespace Slic3r; | ||||
| 
 | ||||
| bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacing, double angle = 0, double density = 1.0); | ||||
| 
 | ||||
| #if 0 | ||||
| TEST_CASE("Fill: adjusted solid distance") { | ||||
|     int surface_width = 250; | ||||
|     int distance = Slic3r::Flow::solid_spacing(surface_width, 47); | ||||
|     REQUIRE(distance == Approx(50)); | ||||
|     REQUIRE(surface_width % distance == 0); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| TEST_CASE("Fill: Pattern Path Length", "[Fill]") { | ||||
|     std::unique_ptr<Slic3r::Fill> filler(Slic3r::Fill::new_from_type("rectilinear")); | ||||
|     filler->angle = float(-(PI)/2.0); | ||||
| 	FillParams fill_params; | ||||
| 	filler->spacing = 5; | ||||
| 	fill_params.dont_adjust = true; | ||||
| 	//fill_params.endpoints_overlap = false;
 | ||||
| 	fill_params.density = float(filler->spacing / 50.0); | ||||
| 
 | ||||
|     auto test = [&filler, &fill_params] (const ExPolygon& poly) -> Slic3r::Polylines { | ||||
|         Slic3r::Surface surface(stTop, poly); | ||||
|         return filler->fill_surface(&surface, fill_params); | ||||
|     }; | ||||
| 
 | ||||
|     SECTION("Square") { | ||||
|         Slic3r::Points test_set; | ||||
|         test_set.reserve(4); | ||||
|         std::vector<Vec2d> points {Vec2d(0,0), Vec2d(100,0), Vec2d(100,100), Vec2d(0,100)}; | ||||
|         for (size_t i = 0; i < 4; ++i) { | ||||
|             std::transform(points.cbegin()+i, points.cend(),   std::back_inserter(test_set), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } );  | ||||
|             std::transform(points.cbegin(), points.cbegin()+i, std::back_inserter(test_set), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } ); | ||||
|             Slic3r::Polylines paths = test(Slic3r::ExPolygon(test_set)); | ||||
|             REQUIRE(paths.size() == 1); // one continuous path
 | ||||
| 
 | ||||
|             // TODO: determine what the "Expected length" should be for rectilinear fill of a 100x100 polygon. 
 | ||||
|             // This check only checks that it's above scale(3*100 + 2*50) + scaled_epsilon.
 | ||||
|             // ok abs($paths->[0]->length - scale(3*100 + 2*50)) - scaled_epsilon, 'path has expected length';
 | ||||
|             REQUIRE(std::abs(paths[0].length() - static_cast<double>(scale_(3*100 + 2*50))) - SCALED_EPSILON > 0); // path has expected length
 | ||||
| 
 | ||||
|             test_set.clear(); | ||||
|         } | ||||
|     } | ||||
|     SECTION("Diamond with endpoints on grid") { | ||||
|         std::vector<Vec2d> points {Vec2d(0,0), Vec2d(100,0), Vec2d(150,50), Vec2d(100,100), Vec2d(0,100), Vec2d(-50,50)}; | ||||
|         Slic3r::Points test_set; | ||||
|         test_set.reserve(6); | ||||
|         std::transform(points.cbegin(), points.cend(),   std::back_inserter(test_set), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } ); | ||||
|         Slic3r::Polylines paths = test(Slic3r::ExPolygon(test_set)); | ||||
|         REQUIRE(paths.size() == 1); // one continuous path
 | ||||
|     } | ||||
| 
 | ||||
|     SECTION("Square with hole") { | ||||
|         std::vector<Vec2d> square {Vec2d(0,0), Vec2d(100,0), Vec2d(100,100), Vec2d(0,100)}; | ||||
|         std::vector<Vec2d> hole {Vec2d(25,25), Vec2d(75,25), Vec2d(75,75), Vec2d(25,75) }; | ||||
|         std::reverse(hole.begin(), hole.end()); | ||||
| 
 | ||||
|         Slic3r::Points test_hole; | ||||
|         Slic3r::Points test_square; | ||||
| 
 | ||||
|         std::transform(square.cbegin(), square.cend(), std::back_inserter(test_square), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } ); | ||||
|         std::transform(hole.cbegin(), hole.cend(), std::back_inserter(test_hole), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } ); | ||||
| 
 | ||||
|         for (double angle : {-(PI/2.0), -(PI/4.0), -(PI), PI/2.0, PI}) { | ||||
|             for (double spacing : {25.0, 5.0, 7.5, 8.5}) { | ||||
| 				fill_params.density = float(filler->spacing / spacing); | ||||
|                 filler->angle = float(angle); | ||||
|                 ExPolygon e(test_square, test_hole); | ||||
|                 Slic3r::Polylines paths = test(e); | ||||
| #if 0 | ||||
| 				{ | ||||
| 					BoundingBox bbox = get_extents(e); | ||||
| 					SVG svg("c:\\data\\temp\\square_with_holes.svg", bbox); | ||||
| 					svg.draw(e); | ||||
| 					svg.draw(paths); | ||||
| 					svg.Close(); | ||||
| 				} | ||||
| #endif | ||||
|                 REQUIRE((paths.size() >= 1 && paths.size() <= 3)); | ||||
|                 // paths don't cross hole
 | ||||
|                 REQUIRE(diff_pl(paths, offset(e, float(SCALED_EPSILON*10))).size() == 0); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     SECTION("Regression: Missing infill segments in some rare circumstances") { | ||||
|         filler->angle = float(PI/4.0); | ||||
| 		fill_params.dont_adjust = false; | ||||
|         filler->spacing = 0.654498; | ||||
|         //filler->endpoints_overlap = unscale(359974);
 | ||||
| 		fill_params.density = 1; | ||||
|         filler->layer_id = 66; | ||||
|         filler->z = 20.15; | ||||
| 
 | ||||
|         Slic3r::Points points {Point(25771516,14142125),Point(14142138,25771515),Point(2512749,14142131),Point(14142125,2512749)}; | ||||
|         Slic3r::Polylines paths = test(Slic3r::ExPolygon(points)); | ||||
|         REQUIRE(paths.size() == 1); // one continuous path
 | ||||
| 
 | ||||
|         // TODO: determine what the "Expected length" should be for rectilinear fill of a 100x100 polygon. 
 | ||||
|         // This check only checks that it's above scale(3*100 + 2*50) + scaled_epsilon.
 | ||||
|         // ok abs($paths->[0]->length - scale(3*100 + 2*50)) - scaled_epsilon, 'path has expected length';
 | ||||
|         REQUIRE(std::abs(paths[0].length() - static_cast<double>(scale_(3*100 + 2*50))) - SCALED_EPSILON > 0); // path has expected length
 | ||||
|     } | ||||
| 
 | ||||
|     SECTION("Rotated Square") { | ||||
|         Slic3r::Points square { Point::new_scale(0,0), Point::new_scale(50,0), Point::new_scale(50,50), Point::new_scale(0,50)}; | ||||
|         Slic3r::ExPolygon expolygon(square); | ||||
|         std::unique_ptr<Slic3r::Fill> filler(Slic3r::Fill::new_from_type("rectilinear")); | ||||
| 		filler->bounding_box = get_extents(expolygon.contour); | ||||
|         filler->angle = 0; | ||||
|          | ||||
|         Surface surface(stTop, expolygon); | ||||
|         auto flow = Slic3r::Flow(0.69, 0.4, 0.50); | ||||
| 
 | ||||
| 		FillParams fill_params; | ||||
| 		fill_params.density = 1.0; | ||||
| 		filler->spacing = flow.spacing(); | ||||
| 
 | ||||
|         for (auto angle : { 0.0, 45.0}) { | ||||
|             surface.expolygon.rotate(angle, Point(0,0)); | ||||
|             Polylines paths = filler->fill_surface(&surface, fill_params); | ||||
|             REQUIRE(paths.size() == 1); | ||||
|         } | ||||
|     } | ||||
|     SECTION("Solid surface fill") { | ||||
|         Slic3r::Points points { | ||||
|             Point::new_scale(6883102, 9598327.01296997), | ||||
|             Point::new_scale(6883102, 20327272.01297), | ||||
|             Point::new_scale(3116896, 20327272.01297), | ||||
|             Point::new_scale(3116896, 9598327.01296997)  | ||||
|         }; | ||||
|         Slic3r::ExPolygon expolygon(points); | ||||
|           | ||||
|         REQUIRE(test_if_solid_surface_filled(expolygon, 0.55) == true); | ||||
|         for (size_t i = 0; i <= 20; ++i) | ||||
|         { | ||||
|             expolygon.scale(1.05); | ||||
|             REQUIRE(test_if_solid_surface_filled(expolygon, 0.55) == true); | ||||
|         } | ||||
|     } | ||||
|     SECTION("Solid surface fill") { | ||||
|         Slic3r::Points points { | ||||
|                 Slic3r::Point(59515297,5422499),Slic3r::Point(59531249,5578697),Slic3r::Point(59695801,6123186), | ||||
|                 Slic3r::Point(59965713,6630228),Slic3r::Point(60328214,7070685),Slic3r::Point(60773285,7434379), | ||||
|                 Slic3r::Point(61274561,7702115),Slic3r::Point(61819378,7866770),Slic3r::Point(62390306,7924789), | ||||
|                 Slic3r::Point(62958700,7866744),Slic3r::Point(63503012,7702244),Slic3r::Point(64007365,7434357), | ||||
|                 Slic3r::Point(64449960,7070398),Slic3r::Point(64809327,6634999),Slic3r::Point(65082143,6123325), | ||||
|                 Slic3r::Point(65245005,5584454),Slic3r::Point(65266967,5422499),Slic3r::Point(66267307,5422499), | ||||
|                 Slic3r::Point(66269190,8310081),Slic3r::Point(66275379,17810072),Slic3r::Point(66277259,20697500), | ||||
|                 Slic3r::Point(65267237,20697500),Slic3r::Point(65245004,20533538),Slic3r::Point(65082082,19994444), | ||||
|                 Slic3r::Point(64811462,19488579),Slic3r::Point(64450624,19048208),Slic3r::Point(64012101,18686514), | ||||
|                 Slic3r::Point(63503122,18415781),Slic3r::Point(62959151,18251378),Slic3r::Point(62453416,18198442), | ||||
|                 Slic3r::Point(62390147,18197355),Slic3r::Point(62200087,18200576),Slic3r::Point(61813519,18252990), | ||||
|                 Slic3r::Point(61274433,18415918),Slic3r::Point(60768598,18686517),Slic3r::Point(60327567,19047892), | ||||
|                 Slic3r::Point(59963609,19493297),Slic3r::Point(59695865,19994587),Slic3r::Point(59531222,20539379), | ||||
|                 Slic3r::Point(59515153,20697500),Slic3r::Point(58502480,20697500),Slic3r::Point(58502480,5422499) | ||||
|         }; | ||||
|         Slic3r::ExPolygon expolygon(points); | ||||
|           | ||||
|         REQUIRE(test_if_solid_surface_filled(expolygon, 0.55) == true); | ||||
|         REQUIRE(test_if_solid_surface_filled(expolygon, 0.55, PI/2.0) == true); | ||||
|     } | ||||
|     SECTION("Solid surface fill") { | ||||
|         Slic3r::Points points { | ||||
|             Point::new_scale(0,0),Point::new_scale(98,0),Point::new_scale(98,10), Point::new_scale(0,10) | ||||
|         }; | ||||
|         Slic3r::ExPolygon expolygon(points); | ||||
|           | ||||
|         REQUIRE(test_if_solid_surface_filled(expolygon, 0.5, 45.0, 0.99) == true); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
| { | ||||
|     my $collection = Slic3r::Polyline::Collection->new( | ||||
|             Slic3r::Polyline->new([0,15], [0,18], [0,20]), | ||||
|             Slic3r::Polyline->new([0,10], [0,8], [0,5]), | ||||
|             ); | ||||
|     is_deeply | ||||
|         [ map $_->[Y], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], | ||||
|         [20, 18, 15, 10, 8, 5], | ||||
|         'chained path'; | ||||
| } | ||||
| 
 | ||||
| { | ||||
|     my $collection = Slic3r::Polyline::Collection->new( | ||||
|             Slic3r::Polyline->new([4,0], [10,0], [15,0]), | ||||
|             Slic3r::Polyline->new([10,5], [15,5], [20,5]), | ||||
|             ); | ||||
|     is_deeply | ||||
|         [ map $_->[X], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], | ||||
|         [reverse 4, 10, 15, 10, 15, 20], | ||||
|         'chained path'; | ||||
| } | ||||
| 
 | ||||
| { | ||||
|     my $collection = Slic3r::ExtrusionPath::Collection->new( | ||||
|             map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), | ||||
|             Slic3r::Polyline->new([0,15], [0,18], [0,20]), | ||||
|             Slic3r::Polyline->new([0,10], [0,8], [0,5]), | ||||
|             ); | ||||
|     is_deeply | ||||
|         [ map $_->[Y], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], | ||||
|         [20, 18, 15, 10, 8, 5], | ||||
|         'chained path'; | ||||
| } | ||||
| 
 | ||||
| { | ||||
|     my $collection = Slic3r::ExtrusionPath::Collection->new( | ||||
|             map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), | ||||
|             Slic3r::Polyline->new([15,0], [10,0], [4,0]), | ||||
|             Slic3r::Polyline->new([10,5], [15,5], [20,5]), | ||||
|             ); | ||||
|     is_deeply | ||||
|         [ map $_->[X], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], | ||||
|         [reverse 4, 10, 15, 10, 15, 20], | ||||
|         'chained path'; | ||||
| } | ||||
| 
 | ||||
| for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { | ||||
|     my $config = Slic3r::Config->new_from_defaults; | ||||
|     $config->set('fill_pattern', $pattern); | ||||
|     $config->set('external_fill_pattern', $pattern); | ||||
|     $config->set('perimeters', 1); | ||||
|     $config->set('skirts', 0); | ||||
|     $config->set('fill_density', 20); | ||||
|     $config->set('layer_height', 0.05); | ||||
|     $config->set('perimeter_extruder', 1); | ||||
|     $config->set('infill_extruder', 2); | ||||
|     my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2); | ||||
|     ok my $gcode = Slic3r::Test::gcode($print), "successful $pattern infill generation"; | ||||
|     my $tool = undef; | ||||
|     my @perimeter_points = my @infill_points = (); | ||||
|     Slic3r::GCode::Reader->new->parse($gcode, sub { | ||||
|             my ($self, $cmd, $args, $info) = @_; | ||||
| 
 | ||||
|             if ($cmd =~ /^T(\d+)/) { | ||||
|             $tool = $1; | ||||
|             } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { | ||||
|             if ($tool == $config->perimeter_extruder-1) { | ||||
|             push @perimeter_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); | ||||
|             } elsif ($tool == $config->infill_extruder-1) { | ||||
|             push @infill_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); | ||||
|             } | ||||
|             } | ||||
|             }); | ||||
|     my $convex_hull = convex_hull(\@perimeter_points); | ||||
|     ok !(defined first { !$convex_hull->contains_point($_) } @infill_points), "infill does not exceed perimeters ($pattern)"; | ||||
| } | ||||
| 
 | ||||
| { | ||||
|     my $config = Slic3r::Config->new_from_defaults; | ||||
|     $config->set('infill_only_where_needed', 1); | ||||
|     $config->set('bottom_solid_layers', 0); | ||||
|     $config->set('infill_extruder', 2); | ||||
|     $config->set('infill_extrusion_width', 0.5); | ||||
|     $config->set('fill_density', 40); | ||||
|     $config->set('cooling', 0);                 # for preventing speeds from being altered | ||||
|         $config->set('first_layer_speed', '100%');  # for preventing speeds from being altered | ||||
| 
 | ||||
|         my $test = sub { | ||||
|             my $print = Slic3r::Test::init_print('pyramid', config => $config); | ||||
| 
 | ||||
|             my $tool = undef; | ||||
|             my @infill_extrusions = ();  # array of polylines | ||||
|                 Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { | ||||
|                         my ($self, $cmd, $args, $info) = @_; | ||||
| 
 | ||||
|                         if ($cmd =~ /^T(\d+)/) { | ||||
|                         $tool = $1; | ||||
|                         } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { | ||||
|                         if ($tool == $config->infill_extruder-1) { | ||||
|                         push @infill_extrusions, Slic3r::Line->new_scale( | ||||
|                                 [ $self->X, $self->Y ], | ||||
|                                 [ $info->{new_X}, $info->{new_Y} ], | ||||
|                                 ); | ||||
|                         } | ||||
|                         } | ||||
|                         }); | ||||
|             return 0 if !@infill_extrusions;  # prevent calling convex_hull() with no points | ||||
| 
 | ||||
|                 my $convex_hull = convex_hull([ map $_->pp, map @$_, @infill_extrusions ]); | ||||
|             return unscale unscale sum(map $_->area, @{offset([$convex_hull], scale(+$config->infill_extrusion_width/2))}); | ||||
|         }; | ||||
| 
 | ||||
|     my $tolerance = 5;  # mm^2 | ||||
| 
 | ||||
|         $config->set('solid_infill_below_area', 0); | ||||
|     ok $test->() < $tolerance, | ||||
|        'no infill is generated when using infill_only_where_needed on a pyramid'; | ||||
| 
 | ||||
|     $config->set('solid_infill_below_area', 70); | ||||
|     ok abs($test->() - $config->solid_infill_below_area) < $tolerance, | ||||
|        'infill is only generated under the forced solid shells'; | ||||
| } | ||||
| 
 | ||||
| { | ||||
|     my $config = Slic3r::Config->new_from_defaults; | ||||
|     $config->set('skirts', 0); | ||||
|     $config->set('perimeters', 1); | ||||
|     $config->set('fill_density', 0); | ||||
|     $config->set('top_solid_layers', 0); | ||||
|     $config->set('bottom_solid_layers', 0); | ||||
|     $config->set('solid_infill_below_area', 20000000); | ||||
|     $config->set('solid_infill_every_layers', 2); | ||||
|     $config->set('perimeter_speed', 99); | ||||
|     $config->set('external_perimeter_speed', 99); | ||||
|     $config->set('cooling', 0); | ||||
|     $config->set('first_layer_speed', '100%'); | ||||
| 
 | ||||
|     my $print = Slic3r::Test::init_print('20mm_cube', config => $config); | ||||
|     my %layers_with_extrusion = (); | ||||
|     Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { | ||||
|             my ($self, $cmd, $args, $info) = @_; | ||||
| 
 | ||||
|             if ($cmd eq 'G1' && $info->{dist_XY} > 0 && $info->{extruding}) { | ||||
|             if (($args->{F} // $self->F) != $config->perimeter_speed*60) {
 | ||||
|             $layers_with_extrusion{$self->Z} = ($args->{F} // $self->F);
 | ||||
|             } | ||||
|             } | ||||
|             }); | ||||
| 
 | ||||
|     ok !%layers_with_extrusion, | ||||
|        "solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0"; | ||||
| } | ||||
| 
 | ||||
| { | ||||
|     my $config = Slic3r::Config->new_from_defaults; | ||||
|     $config->set('skirts', 0); | ||||
|     $config->set('perimeters', 3); | ||||
|     $config->set('fill_density', 0); | ||||
|     $config->set('layer_height', 0.2); | ||||
|     $config->set('first_layer_height', 0.2); | ||||
|     $config->set('nozzle_diameter', [0.35]); | ||||
|     $config->set('infill_extruder', 2); | ||||
|     $config->set('solid_infill_extruder', 2); | ||||
|     $config->set('infill_extrusion_width', 0.52); | ||||
|     $config->set('solid_infill_extrusion_width', 0.52); | ||||
|     $config->set('first_layer_extrusion_width', 0); | ||||
| 
 | ||||
|     my $print = Slic3r::Test::init_print('A', config => $config); | ||||
|     my %infill = ();  # Z => [ Line, Line ... ] | ||||
|         my $tool = undef; | ||||
|     Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { | ||||
|             my ($self, $cmd, $args, $info) = @_; | ||||
| 
 | ||||
|             if ($cmd =~ /^T(\d+)/) { | ||||
|             $tool = $1; | ||||
|             } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { | ||||
|             if ($tool == $config->infill_extruder-1) { | ||||
|             my $z = 1 * $self->Z; | ||||
|             $infill{$z} ||= []; | ||||
|             push @{$infill{$z}}, Slic3r::Line->new_scale( | ||||
|                     [ $self->X, $self->Y ], | ||||
|                     [ $info->{new_X}, $info->{new_Y} ], | ||||
|                     ); | ||||
|             } | ||||
|             } | ||||
|             }); | ||||
|     my $grow_d = scale($config->infill_extrusion_width)/2; | ||||
|     my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]); | ||||
|     my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]); | ||||
|     my $diff = diff($layer0_infill, $layer1_infill); | ||||
|     $diff = offset2_ex($diff, -$grow_d, +$grow_d); | ||||
|     $diff = [ grep { $_->area > 2*(($grow_d*2)**2) } @$diff ]; | ||||
|     is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0'; | ||||
| } | ||||
| 
 | ||||
| { | ||||
|     # GH: #2697 | ||||
|     my $config = Slic3r::Config->new_from_defaults; | ||||
|     $config->set('perimeter_extrusion_width', 0.72); | ||||
|     $config->set('top_infill_extrusion_width', 0.1); | ||||
|     $config->set('infill_extruder', 2);         # in order to distinguish infill | ||||
|         $config->set('solid_infill_extruder', 2);   # in order to distinguish infill | ||||
| 
 | ||||
|         my $print = Slic3r::Test::init_print('20mm_cube', config => $config); | ||||
|     my %infill = ();  # Z => [ Line, Line ... ] | ||||
|         my %other  = ();  # Z => [ Line, Line ... ] | ||||
|         my $tool = undef; | ||||
|     Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { | ||||
|             my ($self, $cmd, $args, $info) = @_; | ||||
| 
 | ||||
|             if ($cmd =~ /^T(\d+)/) { | ||||
|             $tool = $1; | ||||
|             } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { | ||||
|             my $z = 1 * $self->Z; | ||||
|             my $line = Slic3r::Line->new_scale( | ||||
|                     [ $self->X, $self->Y ], | ||||
|                     [ $info->{new_X}, $info->{new_Y} ], | ||||
|                     ); | ||||
|             if ($tool == $config->infill_extruder-1) { | ||||
|             $infill{$z} //= [];
 | ||||
|             push @{$infill{$z}}, $line; | ||||
|             } else { | ||||
|             $other{$z} //= [];
 | ||||
|             push @{$other{$z}}, $line; | ||||
|             } | ||||
|             } | ||||
|             }); | ||||
|     my $top_z = max(keys %infill); | ||||
|     my $top_infill_grow_d = scale($config->top_infill_extrusion_width)/2; | ||||
|     my $top_infill = union([ map @{$_->grow($top_infill_grow_d)}, @{ $infill{$top_z} } ]); | ||||
|     my $perimeters_grow_d = scale($config->perimeter_extrusion_width)/2; | ||||
|     my $perimeters = union([ map @{$_->grow($perimeters_grow_d)}, @{ $other{$top_z} } ]); | ||||
|     my $covered = union_ex([ @$top_infill, @$perimeters ]); | ||||
|     my @holes = map @{$_->holes}, @$covered; | ||||
|     ok sum(map unscale unscale $_->area*-1, @holes) < 1, 'no gaps between top solid infill and perimeters'; | ||||
| } | ||||
| */ | ||||
| 
 | ||||
| bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacing, double angle, double density) | ||||
| { | ||||
|     std::unique_ptr<Slic3r::Fill> filler(Slic3r::Fill::new_from_type("rectilinear")); | ||||
| 	filler->bounding_box = get_extents(expolygon.contour); | ||||
|     filler->angle = float(angle); | ||||
| 
 | ||||
| 	Flow flow(flow_spacing, 0.4, flow_spacing); | ||||
| 	filler->spacing = flow.spacing(); | ||||
| 
 | ||||
| 	FillParams fill_params; | ||||
| 	fill_params.density = float(density); | ||||
| 	fill_params.dont_adjust = false; | ||||
| 
 | ||||
| 	Surface surface(stBottom, expolygon); | ||||
| 	Slic3r::Polylines paths = filler->fill_surface(&surface, fill_params); | ||||
| 
 | ||||
|     // check whether any part was left uncovered
 | ||||
|     Polygons grown_paths; | ||||
|     grown_paths.reserve(paths.size()); | ||||
| 
 | ||||
|     // figure out what is actually going on here re: data types
 | ||||
|     float line_offset = float(scale_(filler->spacing / 2.0 + EPSILON)); | ||||
|     std::for_each(paths.begin(), paths.end(), [line_offset, &grown_paths] (const Slic3r::Polyline& p) { | ||||
|         polygons_append(grown_paths, offset(p, line_offset)); | ||||
|     }); | ||||
| 
 | ||||
| 	// Shrink the initial expolygon a bit, this simulates the infill / perimeter overlap that we usually apply.
 | ||||
|     ExPolygons uncovered = diff_ex(offset(expolygon, - float(0.2 * scale_(flow_spacing))), grown_paths, true); | ||||
| 
 | ||||
|     // ignore very small dots
 | ||||
|     const double scaled_flow_spacing = std::pow(scale_(flow_spacing), 2); | ||||
|     uncovered.erase(std::remove_if(uncovered.begin(), uncovered.end(), [scaled_flow_spacing](const ExPolygon& poly) { return poly.area() < scaled_flow_spacing; }), uncovered.end()); | ||||
| 
 | ||||
| #if 0 | ||||
| 	if (! uncovered.empty()) { | ||||
| 		BoundingBox bbox = get_extents(expolygon.contour); | ||||
| 		bbox.merge(get_extents(uncovered)); | ||||
| 		bbox.merge(get_extents(grown_paths)); | ||||
| 		SVG svg("c:\\data\\temp\\test_if_solid_surface_filled.svg", bbox); | ||||
| 		svg.draw(expolygon); | ||||
| 		svg.draw(uncovered, "red"); | ||||
| 		svg.Close(); | ||||
| 	} | ||||
| #endif | ||||
| 
 | ||||
|     return uncovered.empty(); // solid surface is fully filled
 | ||||
| } | ||||
							
								
								
									
										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
 | ||||
|         DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); | ||||
|         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")); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										61
									
								
								tests/fff_print/test_model.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								tests/fff_print/test_model.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | |||
| #include <catch2/catch.hpp> | ||||
| 
 | ||||
| #include "libslic3r/libslic3r.h" | ||||
| #include "libslic3r/Model.hpp" | ||||
| 
 | ||||
| #include <boost/nowide/cstdio.hpp> | ||||
| #include <boost/filesystem.hpp> | ||||
| 
 | ||||
| #include "test_data.hpp" | ||||
| 
 | ||||
| using namespace Slic3r; | ||||
| using namespace Slic3r::Test; | ||||
| 
 | ||||
| SCENARIO("Model construction", "[Model]") { | ||||
|     GIVEN("A Slic3r Model") { | ||||
| 		Slic3r::Model model; | ||||
|         Slic3r::TriangleMesh sample_mesh = Slic3r::make_cube(20,20,20); | ||||
|         Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); | ||||
|         Slic3r::Print print; | ||||
| 
 | ||||
|         WHEN("Model object is added") { | ||||
|             Slic3r::ModelObject *model_object = model.add_object(); | ||||
|             THEN("Model object list == 1") { | ||||
|                 REQUIRE(model.objects.size() == 1); | ||||
|             } | ||||
|             model_object->add_volume(sample_mesh); | ||||
|             THEN("Model volume list == 1") { | ||||
|                 REQUIRE(model_object->volumes.size() == 1); | ||||
|             } | ||||
|             THEN("Model volume is a part") { | ||||
|                 REQUIRE(model_object->volumes.front()->is_model_part()); | ||||
|             } | ||||
|             THEN("Mesh is equivalent to input mesh.") { | ||||
|                 REQUIRE(! sample_mesh.its.vertices.empty()); | ||||
| 				const std::vector<Vec3f>& mesh_vertices = model_object->volumes.front()->mesh().its.vertices; | ||||
| 				Vec3f mesh_offset = model_object->volumes.front()->source.mesh_offset.cast<float>(); | ||||
| 				for (size_t i = 0; i < sample_mesh.its.vertices.size(); ++ i) { | ||||
| 					const Vec3f &p1 = sample_mesh.its.vertices[i]; | ||||
| 					const Vec3f  p2 = mesh_vertices[i] + mesh_offset; | ||||
| 					REQUIRE((p2 - p1).norm() < EPSILON); | ||||
| 				} | ||||
|             } | ||||
|             Slic3r::ModelInstance *model_instance = model_object->add_instance(); | ||||
| 			model.arrange_objects(PrintConfig::min_object_distance(&config)); | ||||
| 			model.center_instances_around_point(Slic3r::Vec2d(100, 100)); | ||||
| 			model_object->ensure_on_bed(); | ||||
| 			print.auto_assign_extruders(model_object); | ||||
| 			THEN("Print works?") { | ||||
| 				print.set_status_silent(); | ||||
| 				print.apply(model, config); | ||||
| 				print.process(); | ||||
| 				boost::filesystem::path temp = boost::filesystem::unique_path(); | ||||
| 				print.export_gcode(temp.string(), nullptr); | ||||
| 				REQUIRE(boost::filesystem::exists(temp)); | ||||
| 				REQUIRE(boost::filesystem::is_regular_file(temp)); | ||||
| 				REQUIRE(boost::filesystem::file_size(temp) > 0); | ||||
| 				boost::nowide::remove(temp.string().c_str()); | ||||
| 			} | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										144
									
								
								tests/fff_print/test_print.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								tests/fff_print/test_print.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,144 @@ | |||
| #include <catch2/catch.hpp> | ||||
| 
 | ||||
| #include "libslic3r/libslic3r.h" | ||||
| #include "libslic3r/Print.hpp" | ||||
| 
 | ||||
| #include "test_data.hpp" | ||||
| 
 | ||||
| using namespace Slic3r; | ||||
| using namespace Slic3r::Test; | ||||
| 
 | ||||
| SCENARIO("PrintObject: Perimeter generation", "[PrintObject]") { | ||||
|     GIVEN("20mm cube and default config") { | ||||
|         Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); | ||||
|         TestMesh m = TestMesh::cube_20x20x20; | ||||
|         Slic3r::Model model; | ||||
|         size_t event_counter = 0; | ||||
|         std::string stage; | ||||
|         int value = 0; | ||||
|         auto callback = [&event_counter, &stage, &value] (int a, const char* b) { stage = std::string(b); ++ event_counter; value = a; }; | ||||
|         config.set_deserialize("fill_density", "0"); | ||||
| 
 | ||||
|         WHEN("make_perimeters() is called")  { | ||||
|             std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({m}, model, config); | ||||
| 			print->process(); | ||||
| 			const PrintObject& object = *(print->objects().at(0)); | ||||
| 			THEN("67 layers exist in the model") { | ||||
|                 REQUIRE(object.layers().size() == 66); | ||||
|             } | ||||
|             THEN("Every layer in region 0 has 1 island of perimeters") { | ||||
|                 for (const Layer *layer : object.layers()) { | ||||
|                     REQUIRE(layer->regions().front()->perimeters.entities.size() == 1); | ||||
|                 } | ||||
|             } | ||||
|             THEN("Every layer in region 0 has 3 paths in its perimeters list.") { | ||||
|                 for (const Layer *layer : object.layers()) { | ||||
|                     REQUIRE(layer->regions().front()->perimeters.items_count() == 3); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| SCENARIO("Print: Skirt generation", "[Print]") { | ||||
|     GIVEN("20mm cube and default config") { | ||||
|         Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); | ||||
|         TestMesh m = TestMesh::cube_20x20x20; | ||||
|         Slic3r::Model model; | ||||
|         std::string stage; | ||||
|         int value = 0; | ||||
|         config.opt_int("skirt_height") = 1; | ||||
|         config.opt_float("skirt_distance") = 1.f; | ||||
|         WHEN("Skirts is set to 2 loops")  { | ||||
|             config.opt_int("skirts") = 2; | ||||
|             std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({m}, model, config); | ||||
| 			print->process(); | ||||
|             THEN("Skirt Extrusion collection has 2 loops in it") { | ||||
|                 REQUIRE(print->skirt().items_count() == 2); | ||||
|                 REQUIRE(print->skirt().flatten().entities.size() == 2); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void test_is_solid_infill(std::shared_ptr<Slic3r::Print> p, size_t obj_id, size_t layer_id ) { | ||||
|     const PrintObject &obj = *(p->objects().at(obj_id)); | ||||
|     const Layer       &layer = *(obj.get_layer((int)layer_id)); | ||||
| 
 | ||||
|     // iterate over all of the regions in the layer
 | ||||
|     for (const LayerRegion *reg : layer.regions()) { | ||||
|         // for each region, iterate over the fill surfaces
 | ||||
|         for (const Surface& s : reg->fill_surfaces.surfaces) { | ||||
|             CHECK(s.is_solid()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces to become internal.", "[Print]") { | ||||
|     GIVEN("sliced 20mm cube and config with top_solid_surfaces = 2 and bottom_solid_surfaces = 1") { | ||||
|         Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); | ||||
|         TestMesh m = TestMesh::cube_20x20x20; | ||||
|         config.opt_int("top_solid_layers") = 2; | ||||
|         config.opt_int("bottom_solid_layers") = 1; | ||||
|         config.opt_float("layer_height") = 0.5; // get a known number of layers
 | ||||
|         config.set_deserialize("first_layer_height", "0.5"); | ||||
|         Slic3r::Model model; | ||||
|         std::string stage; | ||||
|         std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({m}, model, config); | ||||
|         print->process(); | ||||
|         // Precondition: Ensure that the model has 2 solid top layers (39, 38)
 | ||||
|         // and one solid bottom layer (0).
 | ||||
|         test_is_solid_infill(print, 0, 0); // should be solid
 | ||||
|         test_is_solid_infill(print, 0, 39); // should be solid
 | ||||
|         test_is_solid_infill(print, 0, 38); // should be solid
 | ||||
|         WHEN("Model is re-sliced with top_solid_layers == 3") { | ||||
| 			config.opt_int("top_solid_layers") = 3; | ||||
| 			print->apply(model, config); | ||||
|             print->process(); | ||||
|             THEN("Print object does not have 0 solid bottom layers.") { | ||||
|                 test_is_solid_infill(print, 0, 0); | ||||
|             } | ||||
|             AND_THEN("Print object has 3 top solid layers") { | ||||
|                 test_is_solid_infill(print, 0, 39); | ||||
|                 test_is_solid_infill(print, 0, 38); | ||||
|                 test_is_solid_infill(print, 0, 37); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| SCENARIO("Print: Brim generation", "[Print]") { | ||||
|     GIVEN("20mm cube and default config, 1mm first layer width") { | ||||
|         Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); | ||||
|         TestMesh m = TestMesh::cube_20x20x20; | ||||
|         Slic3r::Model model; | ||||
|         std::string stage; | ||||
|         int value = 0; | ||||
|         config.set_deserialize("first_layer_extrusion_width", "1"); | ||||
|         WHEN("Brim is set to 3mm")  { | ||||
|             config.opt_float("brim_width") = 3; | ||||
|             std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({m}, model, config); | ||||
|             print->process(); | ||||
|             THEN("Brim Extrusion collection has 3 loops in it") { | ||||
|                 REQUIRE(print->brim().items_count() == 3); | ||||
|             } | ||||
|         } | ||||
|         WHEN("Brim is set to 6mm")  { | ||||
|             config.opt_float("brim_width") = 6; | ||||
|             std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({m}, model, config); | ||||
| 			print->process(); | ||||
|             THEN("Brim Extrusion collection has 6 loops in it") { | ||||
|                 REQUIRE(print->brim().items_count() == 6); | ||||
|             } | ||||
|         } | ||||
|         WHEN("Brim is set to 6mm, extrusion width 0.5mm")  { | ||||
|             config.opt_float("brim_width") = 6; | ||||
|             config.set_deserialize("first_layer_extrusion_width", "0.5"); | ||||
|             std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({m}, model, config); | ||||
| 			print->process(); | ||||
|             THEN("Brim Extrusion collection has 12 loops in it") { | ||||
|                 REQUIRE(print->brim().items_count() == 14); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										274
									
								
								tests/fff_print/test_printgcode.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								tests/fff_print/test_printgcode.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,274 @@ | |||
| #include <catch2/catch.hpp> | ||||
| 
 | ||||
| #include "libslic3r/libslic3r.h" | ||||
| #include "libslic3r/GCodeReader.hpp" | ||||
| 
 | ||||
| #include "test_data.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <regex> | ||||
| 
 | ||||
| using namespace Slic3r; | ||||
| using namespace Slic3r::Test; | ||||
| 
 | ||||
| std::regex perimeters_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; perimeter"); | ||||
| std::regex infill_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; infill"); | ||||
| std::regex skirt_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; skirt"); | ||||
| 
 | ||||
| SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") { | ||||
|     GIVEN("A default configuration and a print test object") { | ||||
|         Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); | ||||
| 
 | ||||
|         WHEN("the output is executed with no support material") { | ||||
| 			config.set_deserialize("layer_height", "0.2"); | ||||
| 			config.set_deserialize("first_layer_height", "0.2"); | ||||
|             config.set_deserialize("first_layer_extrusion_width", "0"); | ||||
|             config.set_deserialize("gcode_comments", "1"); | ||||
|             config.set_deserialize("start_gcode", ""); | ||||
|             Slic3r::Model model; | ||||
|             std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config); | ||||
|             std::string gcode = Slic3r::Test::gcode(print); | ||||
|             THEN("Some text output is generated.") { | ||||
|                 REQUIRE(gcode.size() > 0); | ||||
|             } | ||||
|             THEN("Exported text contains slic3r version") { | ||||
|                 REQUIRE(gcode.find(SLIC3R_VERSION) != std::string::npos); | ||||
|             } | ||||
|             //THEN("Exported text contains git commit id") {
 | ||||
|             //    REQUIRE(gcode.find("; Git Commit") != std::string::npos);
 | ||||
|             //    REQUIRE(gcode.find(SLIC3R_BUILD_ID) != std::string::npos);
 | ||||
|             //}
 | ||||
|             THEN("Exported text contains extrusion statistics.") { | ||||
|                 REQUIRE(gcode.find("; external perimeters extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; perimeters extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; infill extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; solid infill extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; top infill extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; support material extrusion width") == std::string::npos); | ||||
|                 REQUIRE(gcode.find("; first layer extrusion width") == std::string::npos); | ||||
|             } | ||||
|             THEN("Exported text does not contain cooling markers (they were consumed)") { | ||||
|                 REQUIRE(gcode.find(";_EXTRUDE_SET_SPEED") == std::string::npos); | ||||
|             } | ||||
| 
 | ||||
|             THEN("GCode preamble is emitted.") { | ||||
|                 REQUIRE(gcode.find("G21 ; set units to millimeters") != std::string::npos); | ||||
|             } | ||||
| 
 | ||||
|             THEN("Config options emitted for print config, default region config, default object config") { | ||||
|                 REQUIRE(gcode.find("; first_layer_temperature") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; layer_height") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; fill_density") != std::string::npos); | ||||
|             } | ||||
|             THEN("Infill is emitted.") { | ||||
|                 std::smatch has_match; | ||||
|                 REQUIRE(std::regex_search(gcode, has_match, infill_regex)); | ||||
|             } | ||||
|             THEN("Perimeters are emitted.") { | ||||
|                 std::smatch has_match; | ||||
|                 REQUIRE(std::regex_search(gcode, has_match, perimeters_regex)); | ||||
|             } | ||||
|             THEN("Skirt is emitted.") { | ||||
|                 std::smatch has_match; | ||||
|                 REQUIRE(std::regex_search(gcode, has_match, skirt_regex)); | ||||
|             } | ||||
|             THEN("final Z height is 20mm") { | ||||
|                 double final_z = 0.0; | ||||
|                 GCodeReader reader; | ||||
|                 reader.apply_config(print->config()); | ||||
|                 reader.parse_buffer(gcode, [&final_z] (GCodeReader& self, const GCodeReader::GCodeLine& line) { | ||||
|                     final_z = std::max<double>(final_z, static_cast<double>(self.z())); // record the highest Z point we reach
 | ||||
|                 }); | ||||
|                 REQUIRE(final_z == Approx(20.)); | ||||
|             } | ||||
|         } | ||||
|         WHEN("output is executed with complete objects and two differently-sized meshes") { | ||||
|             Slic3r::Model model; | ||||
|             config.set_deserialize("first_layer_extrusion_width", "0"); | ||||
|             config.set_deserialize("first_layer_height", "0.3"); | ||||
| 			config.set_deserialize("layer_height", "0.2"); | ||||
| 			config.set_deserialize("support_material", "0"); | ||||
|             config.set_deserialize("raft_layers", "0"); | ||||
|             config.set_deserialize("complete_objects", "1"); | ||||
|             config.set_deserialize("gcode_comments", "1"); | ||||
|             config.set_deserialize("between_objects_gcode", "; between-object-gcode"); | ||||
|             std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({TestMesh::cube_20x20x20,TestMesh::cube_20x20x20}, model, config); | ||||
|             std::string gcode = Slic3r::Test::gcode(print); | ||||
|             THEN("Some text output is generated.") { | ||||
|                 REQUIRE(gcode.size() > 0); | ||||
|             } | ||||
|             THEN("Infill is emitted.") { | ||||
|                 std::smatch has_match; | ||||
|                 REQUIRE(std::regex_search(gcode, has_match, infill_regex)); | ||||
|             } | ||||
|             THEN("Perimeters are emitted.") { | ||||
|                 std::smatch has_match; | ||||
|                 REQUIRE(std::regex_search(gcode, has_match, perimeters_regex)); | ||||
|             } | ||||
|             THEN("Skirt is emitted.") { | ||||
|                 std::smatch has_match; | ||||
|                 REQUIRE(std::regex_search(gcode, has_match, skirt_regex)); | ||||
|             } | ||||
|             THEN("Between-object-gcode is emitted.") { | ||||
|                 REQUIRE(gcode.find("; between-object-gcode") != std::string::npos); | ||||
|             } | ||||
|             THEN("final Z height is 20.1mm") { | ||||
|                 double final_z = 0.0; | ||||
|                 GCodeReader reader; | ||||
|                 reader.apply_config(print->config()); | ||||
|                 reader.parse_buffer(gcode, [&final_z] (GCodeReader& self, const GCodeReader::GCodeLine& line) { | ||||
|                     final_z = std::max(final_z, static_cast<double>(self.z())); // record the highest Z point we reach
 | ||||
|                 }); | ||||
|                 REQUIRE(final_z == Approx(20.1)); | ||||
|             } | ||||
|             THEN("Z height resets on object change") { | ||||
|                 double final_z = 0.0; | ||||
|                 bool reset = false; | ||||
|                 GCodeReader reader; | ||||
|                 reader.apply_config(print->config()); | ||||
|                 reader.parse_buffer(gcode, [&final_z, &reset] (GCodeReader& self, const GCodeReader::GCodeLine& line) { | ||||
|                     if (final_z > 0 && std::abs(self.z() - 0.3) < 0.01 ) { // saw higher Z before this, now it's lower
 | ||||
|                         reset = true; | ||||
|                     } else { | ||||
|                         final_z = std::max(final_z, static_cast<double>(self.z())); // record the highest Z point we reach
 | ||||
|                     } | ||||
|                 }); | ||||
|                 REQUIRE(reset == true); | ||||
|             } | ||||
|             THEN("Shorter object is printed before taller object.") { | ||||
|                 double final_z = 0.0; | ||||
|                 bool reset = false; | ||||
|                 GCodeReader reader; | ||||
|                 reader.apply_config(print->config()); | ||||
|                 reader.parse_buffer(gcode, [&final_z, &reset] (GCodeReader& self, const GCodeReader::GCodeLine& line) { | ||||
|                     if (final_z > 0 && std::abs(self.z() - 0.3) < 0.01 ) {  | ||||
|                         reset = (final_z > 20.0); | ||||
|                     } else { | ||||
|                         final_z = std::max(final_z, static_cast<double>(self.z())); // record the highest Z point we reach
 | ||||
|                     } | ||||
|                 }); | ||||
|                 REQUIRE(reset == true); | ||||
|             } | ||||
|         } | ||||
|         WHEN("the output is executed with support material") { | ||||
|             Slic3r::Model model; | ||||
|             config.set_deserialize("first_layer_extrusion_width", "0"); | ||||
|             config.set_deserialize("support_material", "1"); | ||||
|             config.set_deserialize("raft_layers", "3"); | ||||
|             config.set_deserialize("gcode_comments", "1"); | ||||
|             std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config); | ||||
|             std::string gcode = Slic3r::Test::gcode(print); | ||||
|             THEN("Some text output is generated.") { | ||||
|                 REQUIRE(gcode.size() > 0); | ||||
|             } | ||||
|             THEN("Exported text contains extrusion statistics.") { | ||||
|                 REQUIRE(gcode.find("; external perimeters extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; perimeters extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; infill extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; solid infill extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; top infill extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; support material extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; first layer extrusion width") == std::string::npos); | ||||
|             } | ||||
|             THEN("Raft is emitted.") { | ||||
|                 REQUIRE(gcode.find("; raft") != std::string::npos); | ||||
|             } | ||||
|         } | ||||
|         WHEN("the output is executed with a separate first layer extrusion width") { | ||||
|             Slic3r::Model model; | ||||
|             config.set_deserialize("first_layer_extrusion_width", "0.5"); | ||||
|             std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config); | ||||
|             std::string gcode = Slic3r::Test::gcode(print); | ||||
|             THEN("Some text output is generated.") { | ||||
|                 REQUIRE(gcode.size() > 0); | ||||
|             } | ||||
|             THEN("Exported text contains extrusion statistics.") { | ||||
|                 REQUIRE(gcode.find("; external perimeters extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; perimeters extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; infill extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; solid infill extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; top infill extrusion width") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; support material extrusion width") == std::string::npos); | ||||
|                 REQUIRE(gcode.find("; first layer extrusion width") != std::string::npos); | ||||
|             } | ||||
|         } | ||||
|         WHEN("Cooling is enabled and the fan is disabled.") { | ||||
|             config.set_deserialize("cooling", "1"); | ||||
|             config.set_deserialize("disable_fan_first_layers", "5"); | ||||
|             Slic3r::Model model; | ||||
|             std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config); | ||||
|             std::string gcode = Slic3r::Test::gcode(print); | ||||
|             THEN("GCode to disable fan is emitted."){ | ||||
|                 REQUIRE(gcode.find("M107") != std::string::npos); | ||||
|             } | ||||
|         } | ||||
|         WHEN("end_gcode exists with layer_num and layer_z") { | ||||
|             config.set_deserialize("end_gcode", "; Layer_num [layer_num]\n; Layer_z [layer_z]"); | ||||
|             config.set_deserialize("layer_height", "0.1"); | ||||
|             config.set_deserialize("first_layer_height", "0.1"); | ||||
| 
 | ||||
|             Slic3r::Model model; | ||||
|             std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config); | ||||
|             std::string gcode = Slic3r::Test::gcode(print); | ||||
|             THEN("layer_num and layer_z are processed in the end gcode") { | ||||
|                 REQUIRE(gcode.find("; Layer_num 199") != std::string::npos); | ||||
|                 REQUIRE(gcode.find("; Layer_z 20") != std::string::npos); | ||||
|             } | ||||
|         } | ||||
|         WHEN("current_extruder exists in start_gcode") { | ||||
|             config.set_deserialize("start_gcode", "; Extruder [current_extruder]"); | ||||
|             { | ||||
|                 Slic3r::Model model; | ||||
|                 std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config); | ||||
|                 std::string gcode = Slic3r::Test::gcode(print); | ||||
|                 THEN("current_extruder is processed in the start gcode and set for first extruder") { | ||||
|                     REQUIRE(gcode.find("; Extruder 0") != std::string::npos); | ||||
|                 } | ||||
|             } | ||||
| 			config.set_num_extruders(4); | ||||
| 			config.set_deserialize("infill_extruder", "2"); | ||||
| 			config.set_deserialize("solid_infill_extruder", "2"); | ||||
| 			config.set_deserialize("perimeter_extruder", "2"); | ||||
| 			config.set_deserialize("support_material_extruder", "2"); | ||||
| 			config.set_deserialize("support_material_interface_extruder", "2"); | ||||
| 			{ | ||||
|                 Slic3r::Model model; | ||||
|                 std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config); | ||||
|                 std::string gcode = Slic3r::Test::gcode(print); | ||||
|                 THEN("current_extruder is processed in the start gcode and set for second extruder") { | ||||
|                     REQUIRE(gcode.find("; Extruder 1") != std::string::npos); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         WHEN("layer_num represents the layer's index from z=0") { | ||||
| 			config.set_deserialize("complete_objects", "1"); | ||||
| 			config.set_deserialize("gcode_comments", "1"); | ||||
| 			config.set_deserialize("layer_gcode", ";Layer:[layer_num] ([layer_z] mm)"); | ||||
|             config.set_deserialize("layer_height", "1.0"); | ||||
|             config.set_deserialize("first_layer_height", "1.0"); | ||||
| 
 | ||||
|             Slic3r::Model model; | ||||
|             std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({TestMesh::cube_20x20x20,TestMesh::cube_20x20x20}, model, config); | ||||
|             std::string gcode = Slic3r::Test::gcode(print); | ||||
| 			// End of the 1st object.
 | ||||
| 			size_t pos = gcode.find(";Layer:19 "); | ||||
| 			THEN("First and second object last layer is emitted") { | ||||
| 				// First object
 | ||||
| 				REQUIRE(pos != std::string::npos); | ||||
| 				pos += 10; | ||||
| 				REQUIRE(pos < gcode.size()); | ||||
| 				double z = 0; | ||||
| 				REQUIRE((sscanf(gcode.data() + pos, "(%lf mm)", &z) == 1)); | ||||
| 				REQUIRE(z == Approx(20.)); | ||||
| 				// Second object
 | ||||
| 				pos = gcode.find(";Layer:39 ", pos); | ||||
| 				REQUIRE(pos != std::string::npos); | ||||
| 				pos += 10; | ||||
| 				REQUIRE(pos < gcode.size()); | ||||
| 				REQUIRE((sscanf(gcode.data() + pos, "(%lf mm)", &z) == 1)); | ||||
| 				REQUIRE(z == Approx(20.)); | ||||
| 			} | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										86
									
								
								tests/fff_print/test_printobject.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								tests/fff_print/test_printobject.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,86 @@ | |||
| #include <catch2/catch.hpp> | ||||
| 
 | ||||
| #include "libslic3r/libslic3r.h" | ||||
| #include "libslic3r/Print.hpp" | ||||
| 
 | ||||
| #include "test_data.hpp" | ||||
| 
 | ||||
| using namespace Slic3r; | ||||
| using namespace Slic3r::Test; | ||||
| 
 | ||||
| SCENARIO("PrintObject: object layer heights", "[PrintObject]") { | ||||
|     GIVEN("20mm cube and default initial config, initial layer height of 2mm") { | ||||
|         Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); | ||||
|         TestMesh m = TestMesh::cube_20x20x20; | ||||
|         Slic3r::Model model; | ||||
| 
 | ||||
|         config.set_deserialize("first_layer_height", "2"); | ||||
| 
 | ||||
|         WHEN("generate_object_layers() is called for 2mm layer heights and nozzle diameter of 3mm") { | ||||
|             config.opt_float("nozzle_diameter", 0) = 3; | ||||
| 			config.opt_float("layer_height") = 2.0; | ||||
| 			std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({m}, model, config); | ||||
| 			print->process(); | ||||
| 			const std::vector<Slic3r::Layer*> &layers = print->objects().front()->layers(); | ||||
|             THEN("The output vector has 10 entries") { | ||||
|                 REQUIRE(layers.size() == 10); | ||||
|             } | ||||
|             AND_THEN("Each layer is approximately 2mm above the previous Z") { | ||||
|                 coordf_t last = 0.0; | ||||
|                 for (size_t i = 0; i < layers.size(); ++ i) { | ||||
|                     REQUIRE((layers[i]->print_z - last) == Approx(2.0)); | ||||
|                     last = layers[i]->print_z; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         WHEN("generate_object_layers() is called for 10mm layer heights and nozzle diameter of 11mm") { | ||||
|             config.opt_float("nozzle_diameter", 0) = 11; | ||||
| 			config.opt_float("layer_height") = 10; | ||||
|             std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({m}, model, config); | ||||
| 			print->process(); | ||||
| 			const std::vector<Slic3r::Layer*> &layers = print->objects().front()->layers(); | ||||
| 			THEN("The output vector has 3 entries") { | ||||
|                 REQUIRE(layers.size() == 3); | ||||
|             } | ||||
|             AND_THEN("Layer 0 is at 2mm") { | ||||
|                 REQUIRE(layers.front()->print_z == Approx(2.0)); | ||||
|             } | ||||
|             AND_THEN("Layer 1 is at 12mm") { | ||||
|                 REQUIRE(layers[1]->print_z == Approx(12.0)); | ||||
|             } | ||||
|         } | ||||
|         WHEN("generate_object_layers() is called for 15mm layer heights and nozzle diameter of 16mm") { | ||||
|             config.opt_float("nozzle_diameter", 0) = 16; | ||||
|             config.opt_float("layer_height") = 15.0; | ||||
|             std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({m}, model, config); | ||||
| 			print->process(); | ||||
| 			const std::vector<Slic3r::Layer*> &layers = print->objects().front()->layers(); | ||||
| 			THEN("The output vector has 2 entries") { | ||||
|                 REQUIRE(layers.size() == 2); | ||||
|             } | ||||
|             AND_THEN("Layer 0 is at 2mm") { | ||||
|                 REQUIRE(layers[0]->print_z == Approx(2.0)); | ||||
|             } | ||||
|             AND_THEN("Layer 1 is at 17mm") { | ||||
|                 REQUIRE(layers[1]->print_z == Approx(17.0)); | ||||
|             } | ||||
|         } | ||||
| #if 0 | ||||
|         WHEN("generate_object_layers() is called for 15mm layer heights and nozzle diameter of 5mm") { | ||||
|             config.opt_float("nozzle_diameter", 0) = 5; | ||||
|             config.opt_float("layer_height") = 15.0; | ||||
|             std::shared_ptr<Slic3r::Print> print = Slic3r::Test::init_print({m}, model, config); | ||||
| 			print->process(); | ||||
| 			const std::vector<Slic3r::Layer*> &layers = print->objects().front()->layers(); | ||||
| 			THEN("The layer height is limited to 5mm.") { | ||||
|                 CHECK(layers.size() == 5); | ||||
|                 coordf_t last = 2.0; | ||||
|                 for (size_t i = 1; i < layers.size(); i++) { | ||||
|                     REQUIRE((layers[i]->print_z - last) == Approx(5.0)); | ||||
|                     last = layers[i]->print_z; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| #endif | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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") { | ||||
|     DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); | ||||
|     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") { | ||||
| 	    DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); | ||||
| 		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; | ||||
|     DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); | ||||
|     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; | ||||
|     DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); | ||||
|     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())); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										12
									
								
								tests/libslic3r/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tests/libslic3r/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) | ||||
| add_executable(${_TEST_NAME}_tests  | ||||
| 	${_TEST_NAME}_tests.cpp | ||||
| 	test_3mf.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); | ||||
| } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										20
									
								
								tests/libslic3r/test_3mf.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								tests/libslic3r/test_3mf.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| #include <catch2/catch.hpp> | ||||
| 
 | ||||
| #include "libslic3r/Model.hpp" | ||||
| #include "libslic3r/Format/3mf.hpp" | ||||
| 
 | ||||
| using namespace Slic3r; | ||||
| 
 | ||||
| SCENARIO("Reading 3mf file") { | ||||
|     GIVEN("umlauts in the path of the file") { | ||||
|         Slic3r::Model model; | ||||
|         WHEN("3mf model is read") { | ||||
|         	std::string path = std::string(TEST_DATA_DIR) + "/test_3mf/Geräte/Büchse.3mf"; | ||||
|         	DynamicPrintConfig config; | ||||
|             bool ret = Slic3r::load_3mf(path.c_str(), &config, &model, false); | ||||
|             THEN("load should succeed") { | ||||
|                 REQUIRE(ret); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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") | ||||
|  |  | |||
|  | @ -8,7 +8,8 @@ | |||
| %name{Slic3r::Config} class DynamicPrintConfig { | ||||
|     DynamicPrintConfig(); | ||||
|     ~DynamicPrintConfig(); | ||||
|     static DynamicPrintConfig* new_from_defaults(); | ||||
|     static DynamicPrintConfig* new_from_defaults() | ||||
|         %code{% RETVAL = DynamicPrintConfig::new_from_defaults_keys(FullPrintConfig::defaults().keys()); %}; | ||||
|     static DynamicPrintConfig* new_from_defaults_keys(std::vector<std::string> keys); | ||||
|     DynamicPrintConfig* clone() %code{% RETVAL = new DynamicPrintConfig(*THIS); %}; | ||||
|     DynamicPrintConfig* clone_only(std::vector<std::string> keys)  | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Enrico Turri
						Enrico Turri