Merge remote-tracking branch 'origin/feature_arrange_with_libnest2d' into dev

# Conflicts:
#	xs/src/slic3r/AppController.cpp
This commit is contained in:
tamasmeszaros 2018-08-31 10:55:55 +02:00
commit 6e2ed48e5a
30 changed files with 3273 additions and 1687 deletions

View File

@ -813,6 +813,7 @@ add_custom_target(pot
set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d")
add_subdirectory(${LIBDIR}/libnest2d)
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB)
target_include_directories(libslic3r PUBLIC BEFORE ${LIBNEST2D_INCLUDES})
target_include_directories(libslic3r_gui PUBLIC BEFORE ${LIBNEST2D_INCLUDES})

View File

@ -31,6 +31,7 @@ set(LIBNEST2D_SRCFILES
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/common.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizer.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/metaloop.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/rotfinder.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/placer_boilerplate.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/bottomleftplacer.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/nfpplacer.hpp
@ -89,14 +90,39 @@ if(LIBNEST2D_UNITTESTS)
endif()
if(LIBNEST2D_BUILD_EXAMPLES)
add_executable(example examples/main.cpp
# tools/libnfpglue.hpp
# tools/libnfpglue.cpp
tools/nfp_svgnest.hpp
tools/nfp_svgnest_glue.hpp
tools/svgtools.hpp
tests/printer_parts.cpp
tests/printer_parts.h
${LIBNEST2D_SRCFILES})
${LIBNEST2D_SRCFILES}
)
set(TBB_STATIC ON)
find_package(TBB QUIET)
if(TBB_FOUND)
message(STATUS "Parallelization with Intel TBB")
target_include_directories(example PUBLIC ${TBB_INCLUDE_DIRS})
target_compile_definitions(example PUBLIC ${TBB_DEFINITIONS} -DUSE_TBB)
if(MSVC)
# Suppress implicit linking of the TBB libraries by the Visual Studio compiler.
target_compile_definitions(example PUBLIC -D__TBB_NO_IMPLICIT_LINKAGE)
endif()
# The Intel TBB library will use the std::exception_ptr feature of C++11.
target_compile_definitions(example PUBLIC -DTBB_USE_CAPTURED_EXCEPTION=1)
target_link_libraries(example ${TBB_LIBRARIES})
else()
find_package(OpenMP QUIET)
if(OpenMP_CXX_FOUND)
message(STATUS "Parallelization with OpenMP")
target_include_directories(example PUBLIC OpenMP::OpenMP_CXX)
target_link_libraries(example OpenMP::OpenMP_CXX)
endif()
endif()
target_link_libraries(example ${LIBNEST2D_LIBRARIES})
target_include_directories(example PUBLIC ${LIBNEST2D_HEADERS})

View File

@ -9,18 +9,28 @@ with templated geometry types. These geometries can have custom or already
existing implementation to avoid copying or having unnecessary dependencies.
A default backend is provided if the user of the library just wants to use it
out of the box without additional integration. The default backend is reasonably
out of the box without additional integration. This backend is reasonably
fast and robust, being built on top of boost geometry and the
[polyclipping](http://www.angusj.com/delphi/clipper.php) library. Usage of
this default backend implies the dependency on these packages as well as the
compilation of the backend itself (The default backend is not yet header only).
this default backend implies the dependency on these packages but its header
only as well.
This software is currently under construction and lacks a throughout
documentation and some essential algorithms as well. At this stage it works well
for rectangles and convex closed polygons without considering holes and
concavities.
Holes and non-convex polygons will be usable in the near future as well.
Holes and non-convex polygons will be usable in the near future as well. The
no fit polygon based placer module combined with the first fit selection
strategy is now used in the [Slic3r](https://github.com/prusa3d/Slic3r)
application's arrangement feature. It uses local optimization techniques to find
the best placement of each new item based on some features of the arrangement.
In the near future I would like to use machine learning to evaluate the
placements and (or) the order if items in which they are placed and see what
results can be obtained. This is a different approach than that of SVGnest which
uses genetic algorithms to find better and better selection orders. Maybe the
two approaches can be combined as well.
# References
- [SVGNest](https://github.com/Jack000/SVGnest)

View File

@ -0,0 +1,322 @@
# The MIT License (MIT)
#
# Copyright (c) 2015 Justus Calvin
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# FindTBB
# -------
#
# Find TBB include directories and libraries.
#
# Usage:
#
# find_package(TBB [major[.minor]] [EXACT]
# [QUIET] [REQUIRED]
# [[COMPONENTS] [components...]]
# [OPTIONAL_COMPONENTS components...])
#
# where the allowed components are tbbmalloc and tbb_preview. Users may modify
# the behavior of this module with the following variables:
#
# * TBB_ROOT_DIR - The base directory the of TBB installation.
# * TBB_INCLUDE_DIR - The directory that contains the TBB headers files.
# * TBB_LIBRARY - The directory that contains the TBB library files.
# * TBB_<library>_LIBRARY - The path of the TBB the corresponding TBB library.
# These libraries, if specified, override the
# corresponding library search results, where <library>
# may be tbb, tbb_debug, tbbmalloc, tbbmalloc_debug,
# tbb_preview, or tbb_preview_debug.
# * TBB_USE_DEBUG_BUILD - The debug version of tbb libraries, if present, will
# be used instead of the release version.
# * TBB_STATIC - Static linking of libraries with a _static suffix.
# For example, on Windows a tbb_static.lib will be searched for
# instead of tbb.lib.
#
# Users may modify the behavior of this module with the following environment
# variables:
#
# * TBB_INSTALL_DIR
# * TBBROOT
# * LIBRARY_PATH
#
# This module will set the following variables:
#
# * TBB_FOUND - Set to false, or undefined, if we havent found, or
# dont want to use TBB.
# * TBB_<component>_FOUND - If False, optional <component> part of TBB sytem is
# not available.
# * TBB_VERSION - The full version string
# * TBB_VERSION_MAJOR - The major version
# * TBB_VERSION_MINOR - The minor version
# * TBB_INTERFACE_VERSION - The interface version number defined in
# tbb/tbb_stddef.h.
# * TBB_<library>_LIBRARY_RELEASE - The path of the TBB release version of
# <library>, where <library> may be tbb, tbb_debug,
# tbbmalloc, tbbmalloc_debug, tbb_preview, or
# tbb_preview_debug.
# * TBB_<library>_LIBRARY_DEGUG - The path of the TBB release version of
# <library>, where <library> may be tbb, tbb_debug,
# tbbmalloc, tbbmalloc_debug, tbb_preview, or
# tbb_preview_debug.
#
# The following varibles should be used to build and link with TBB:
#
# * TBB_INCLUDE_DIRS - The include directory for TBB.
# * TBB_LIBRARIES - The libraries to link against to use TBB.
# * TBB_LIBRARIES_RELEASE - The release libraries to link against to use TBB.
# * TBB_LIBRARIES_DEBUG - The debug libraries to link against to use TBB.
# * TBB_DEFINITIONS - Definitions to use when compiling code that uses
# TBB.
# * TBB_DEFINITIONS_RELEASE - Definitions to use when compiling release code that
# uses TBB.
# * TBB_DEFINITIONS_DEBUG - Definitions to use when compiling debug code that
# uses TBB.
#
# This module will also create the "tbb" target that may be used when building
# executables and libraries.
include(FindPackageHandleStandardArgs)
if(NOT TBB_FOUND)
##################################
# Check the build type
##################################
if(NOT DEFINED TBB_USE_DEBUG_BUILD)
if(CMAKE_BUILD_TYPE MATCHES "(Debug|DEBUG|debug)")
set(TBB_BUILD_TYPE DEBUG)
else()
set(TBB_BUILD_TYPE RELEASE)
endif()
elseif(TBB_USE_DEBUG_BUILD)
set(TBB_BUILD_TYPE DEBUG)
else()
set(TBB_BUILD_TYPE RELEASE)
endif()
##################################
# Set the TBB search directories
##################################
# Define search paths based on user input and environment variables
set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT})
# Define the search directories based on the current platform
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB"
"C:/Program Files (x86)/Intel/TBB")
# Set the target architecture
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(TBB_ARCHITECTURE "intel64")
else()
set(TBB_ARCHITECTURE "ia32")
endif()
# Set the TBB search library path search suffix based on the version of VC
if(WINDOWS_STORE)
set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11_ui")
elseif(MSVC14)
set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc14")
elseif(MSVC12)
set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc12")
elseif(MSVC11)
set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11")
elseif(MSVC10)
set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc10")
endif()
# Add the library path search suffix for the VC independent version of TBB
list(APPEND TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc_mt")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
# OS X
set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
# TODO: Check to see which C++ library is being used by the compiler.
if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0)
# The default C++ library on OS X 10.9 and later is libc++
set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib")
else()
set(TBB_LIB_PATH_SUFFIX "lib")
endif()
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
# Linux
set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
# TODO: Check compiler version to see the suffix should be <arch>/gcc4.1 or
# <arch>/gcc4.1. For now, assume that the compiler is more recent than
# gcc 4.4.x or later.
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$")
set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4")
endif()
endif()
##################################
# Find the TBB include dir
##################################
find_path(TBB_INCLUDE_DIRS tbb/tbb.h
HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR}
PATHS ${TBB_DEFAULT_SEARCH_DIR}
PATH_SUFFIXES include)
##################################
# Set version strings
##################################
if(TBB_INCLUDE_DIRS)
file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file)
string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1"
TBB_VERSION_MAJOR "${_tbb_version_file}")
string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1"
TBB_VERSION_MINOR "${_tbb_version_file}")
string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1"
TBB_INTERFACE_VERSION "${_tbb_version_file}")
set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}")
endif()
##################################
# Find TBB components
##################################
if(TBB_VERSION VERSION_LESS 4.3)
set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb)
else()
set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb)
endif()
if(TBB_STATIC)
set(TBB_STATIC_SUFFIX "_static")
endif()
# Find each component
foreach(_comp ${TBB_SEARCH_COMPOMPONENTS})
if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};")
# Search for the libraries
find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX}
HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug
HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
if(TBB_${_comp}_LIBRARY_DEBUG)
list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}")
endif()
if(TBB_${_comp}_LIBRARY_RELEASE)
list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}")
endif()
if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY)
set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}")
endif()
if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}")
set(TBB_${_comp}_FOUND TRUE)
else()
set(TBB_${_comp}_FOUND FALSE)
endif()
# Mark internal variables as advanced
mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE)
mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG)
mark_as_advanced(TBB_${_comp}_LIBRARY)
endif()
endforeach()
unset(TBB_STATIC_SUFFIX)
##################################
# Set compile flags and libraries
##################################
set(TBB_DEFINITIONS_RELEASE "")
set(TBB_DEFINITIONS_DEBUG "-DTBB_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()
find_package_handle_standard_args(TBB
REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES
HANDLE_COMPONENTS
VERSION_VAR TBB_VERSION)
##################################
# Create targets
##################################
if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND)
add_library(tbb SHARED IMPORTED)
set_target_properties(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>"
IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG}
IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_DEBUG}
IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE}
IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE}
)
elseif(TBB_LIBRARIES_RELEASE)
set_target_properties(tbb PROPERTIES IMPORTED_LOCATION ${TBB_LIBRARIES_RELEASE})
else()
set_target_properties(tbb PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}"
IMPORTED_LOCATION ${TBB_LIBRARIES_DEBUG}
)
endif()
endif()
mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES)
unset(TBB_ARCHITECTURE)
unset(TBB_BUILD_TYPE)
unset(TBB_LIB_PATH_SUFFIX)
unset(TBB_DEFAULT_SEARCH_DIR)
if(TBB_DEBUG)
message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}")
message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}")
message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}")
message(STATUS " TBB_DEFINITIONS_DEBUG = ${TBB_DEFINITIONS_DEBUG}")
message(STATUS " TBB_LIBRARIES_DEBUG = ${TBB_LIBRARIES_DEBUG}")
message(STATUS " TBB_DEFINITIONS_RELEASE = ${TBB_DEFINITIONS_RELEASE}")
message(STATUS " TBB_LIBRARIES_RELEASE = ${TBB_LIBRARIES_RELEASE}")
endif()
endif()

View File

@ -1,7 +1,6 @@
#include <iostream>
#include <string>
#include <fstream>
//#define DEBUG_EXPORT_NFP
#include <libnest2d.h>
@ -9,7 +8,11 @@
#include "tests/printer_parts.h"
#include "tools/benchmark.h"
#include "tools/svgtools.hpp"
#include "libnest2d/rotfinder.hpp"
//#include "tools/libnfpglue.hpp"
//#include "tools/nfp_svgnest_glue.hpp"
using namespace libnest2d;
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
@ -50,499 +53,57 @@ void arrangeRectangles() {
using namespace libnest2d;
const int SCALE = 1000000;
// const int SCALE = 1;
std::vector<Rectangle> rects = {
{80*SCALE, 80*SCALE},
{60*SCALE, 90*SCALE},
{70*SCALE, 30*SCALE},
{80*SCALE, 60*SCALE},
{60*SCALE, 60*SCALE},
{60*SCALE, 40*SCALE},
{40*SCALE, 40*SCALE},
{10*SCALE, 10*SCALE},
{10*SCALE, 10*SCALE},
{10*SCALE, 10*SCALE},
{10*SCALE, 10*SCALE},
{10*SCALE, 10*SCALE},
{5*SCALE, 5*SCALE},
{5*SCALE, 5*SCALE},
{5*SCALE, 5*SCALE},
{5*SCALE, 5*SCALE},
{5*SCALE, 5*SCALE},
{5*SCALE, 5*SCALE},
{5*SCALE, 5*SCALE},
{20*SCALE, 20*SCALE}
};
// std::vector<Rectangle> rects = {
// {20*SCALE, 10*SCALE},
// {20*SCALE, 10*SCALE},
// {20*SCALE, 20*SCALE},
// };
std::vector<Item> rects(202, {
{-9945219, -3065619},
{-9781479, -2031780},
{-9510560, -1020730},
{-9135450, -43529},
{-2099999, 14110899},
{2099999, 14110899},
{9135450, -43529},
{9510560, -1020730},
{9781479, -2031780},
{9945219, -3065619},
{10000000, -4110899},
{9945219, -5156179},
{9781479, -6190019},
{9510560, -7201069},
{9135450, -8178270},
{8660249, -9110899},
{8090169, -9988750},
{7431449, -10802209},
{6691309, -11542349},
{5877850, -12201069},
{5000000, -12771149},
{4067369, -13246350},
{3090169, -13621459},
{2079119, -13892379},
{1045279, -14056119},
{0, -14110899},
{-1045279, -14056119},
{-2079119, -13892379},
{-3090169, -13621459},
{-4067369, -13246350},
{-5000000, -12771149},
{-5877850, -12201069},
{-6691309, -11542349},
{-7431449, -10802209},
{-8090169, -9988750},
{-8660249, -9110899},
{-9135450, -8178270},
{-9510560, -7201069},
{-9781479, -6190019},
{-9945219, -5156179},
{-10000000, -4110899},
{-9945219, -3065619},
});
// std::vector<Item> input {
// {{0, 0}, {0, 20*SCALE}, {10*SCALE, 0}, {0, 0}}
// };
std::vector<Item> crasher =
{
{
{-5000000, 8954050},
{5000000, 8954050},
{5000000, -45949},
{4972609, -568549},
{3500000, -8954050},
{-3500000, -8954050},
{-4972609, -568549},
{-5000000, -45949},
{-5000000, 8954050},
},
{
{-5000000, 8954050},
{5000000, 8954050},
{5000000, -45949},
{4972609, -568549},
{3500000, -8954050},
{-3500000, -8954050},
{-4972609, -568549},
{-5000000, -45949},
{-5000000, 8954050},
},
{
{-5000000, 8954050},
{5000000, 8954050},
{5000000, -45949},
{4972609, -568549},
{3500000, -8954050},
{-3500000, -8954050},
{-4972609, -568549},
{-5000000, -45949},
{-5000000, 8954050},
},
{
{-5000000, 8954050},
{5000000, 8954050},
{5000000, -45949},
{4972609, -568549},
{3500000, -8954050},
{-3500000, -8954050},
{-4972609, -568549},
{-5000000, -45949},
{-5000000, 8954050},
},
{
{-5000000, 8954050},
{5000000, 8954050},
{5000000, -45949},
{4972609, -568549},
{3500000, -8954050},
{-3500000, -8954050},
{-4972609, -568549},
{-5000000, -45949},
{-5000000, 8954050},
},
{
{-5000000, 8954050},
{5000000, 8954050},
{5000000, -45949},
{4972609, -568549},
{3500000, -8954050},
{-3500000, -8954050},
{-4972609, -568549},
{-5000000, -45949},
{-5000000, 8954050},
},
{
{-9945219, -3065619},
{-9781479, -2031780},
{-9510560, -1020730},
{-9135450, -43529},
{-2099999, 14110899},
{2099999, 14110899},
{9135450, -43529},
{9510560, -1020730},
{9781479, -2031780},
{9945219, -3065619},
{10000000, -4110899},
{9945219, -5156179},
{9781479, -6190020},
{9510560, -7201069},
{9135450, -8178270},
{8660249, -9110899},
{8090169, -9988750},
{7431449, -10802200},
{6691309, -11542300},
{5877850, -12201100},
{5000000, -12771100},
{4067369, -13246399},
{3090169, -13621500},
{2079119, -13892399},
{1045279, -14056099},
{0, -14110899},
{-1045279, -14056099},
{-2079119, -13892399},
{-3090169, -13621500},
{-4067369, -13246399},
{-5000000, -12771100},
{-5877850, -12201100},
{-6691309, -11542300},
{-7431449, -10802200},
{-8090169, -9988750},
{-8660249, -9110899},
{-9135450, -8178270},
{-9510560, -7201069},
{-9781479, -6190020},
{-9945219, -5156179},
{-10000000, -4110899},
{-9945219, -3065619},
},
{
{-9945219, -3065619},
{-9781479, -2031780},
{-9510560, -1020730},
{-9135450, -43529},
{-2099999, 14110899},
{2099999, 14110899},
{9135450, -43529},
{9510560, -1020730},
{9781479, -2031780},
{9945219, -3065619},
{10000000, -4110899},
{9945219, -5156179},
{9781479, -6190020},
{9510560, -7201069},
{9135450, -8178270},
{8660249, -9110899},
{8090169, -9988750},
{7431449, -10802200},
{6691309, -11542300},
{5877850, -12201100},
{5000000, -12771100},
{4067369, -13246399},
{3090169, -13621500},
{2079119, -13892399},
{1045279, -14056099},
{0, -14110899},
{-1045279, -14056099},
{-2079119, -13892399},
{-3090169, -13621500},
{-4067369, -13246399},
{-5000000, -12771100},
{-5877850, -12201100},
{-6691309, -11542300},
{-7431449, -10802200},
{-8090169, -9988750},
{-8660249, -9110899},
{-9135450, -8178270},
{-9510560, -7201069},
{-9781479, -6190020},
{-9945219, -5156179},
{-10000000, -4110899},
{-9945219, -3065619},
},
{
{-9945219, -3065619},
{-9781479, -2031780},
{-9510560, -1020730},
{-9135450, -43529},
{-2099999, 14110899},
{2099999, 14110899},
{9135450, -43529},
{9510560, -1020730},
{9781479, -2031780},
{9945219, -3065619},
{10000000, -4110899},
{9945219, -5156179},
{9781479, -6190020},
{9510560, -7201069},
{9135450, -8178270},
{8660249, -9110899},
{8090169, -9988750},
{7431449, -10802200},
{6691309, -11542300},
{5877850, -12201100},
{5000000, -12771100},
{4067369, -13246399},
{3090169, -13621500},
{2079119, -13892399},
{1045279, -14056099},
{0, -14110899},
{-1045279, -14056099},
{-2079119, -13892399},
{-3090169, -13621500},
{-4067369, -13246399},
{-5000000, -12771100},
{-5877850, -12201100},
{-6691309, -11542300},
{-7431449, -10802200},
{-8090169, -9988750},
{-8660249, -9110899},
{-9135450, -8178270},
{-9510560, -7201069},
{-9781479, -6190020},
{-9945219, -5156179},
{-10000000, -4110899},
{-9945219, -3065619},
},
{
{-9945219, -3065619},
{-9781479, -2031780},
{-9510560, -1020730},
{-9135450, -43529},
{-2099999, 14110899},
{2099999, 14110899},
{9135450, -43529},
{9510560, -1020730},
{9781479, -2031780},
{9945219, -3065619},
{10000000, -4110899},
{9945219, -5156179},
{9781479, -6190020},
{9510560, -7201069},
{9135450, -8178270},
{8660249, -9110899},
{8090169, -9988750},
{7431449, -10802200},
{6691309, -11542300},
{5877850, -12201100},
{5000000, -12771100},
{4067369, -13246399},
{3090169, -13621500},
{2079119, -13892399},
{1045279, -14056099},
{0, -14110899},
{-1045279, -14056099},
{-2079119, -13892399},
{-3090169, -13621500},
{-4067369, -13246399},
{-5000000, -12771100},
{-5877850, -12201100},
{-6691309, -11542300},
{-7431449, -10802200},
{-8090169, -9988750},
{-8660249, -9110899},
{-9135450, -8178270},
{-9510560, -7201069},
{-9781479, -6190020},
{-9945219, -5156179},
{-10000000, -4110899},
{-9945219, -3065619},
},
{
{-9945219, -3065619},
{-9781479, -2031780},
{-9510560, -1020730},
{-9135450, -43529},
{-2099999, 14110899},
{2099999, 14110899},
{9135450, -43529},
{9510560, -1020730},
{9781479, -2031780},
{9945219, -3065619},
{10000000, -4110899},
{9945219, -5156179},
{9781479, -6190020},
{9510560, -7201069},
{9135450, -8178270},
{8660249, -9110899},
{8090169, -9988750},
{7431449, -10802200},
{6691309, -11542300},
{5877850, -12201100},
{5000000, -12771100},
{4067369, -13246399},
{3090169, -13621500},
{2079119, -13892399},
{1045279, -14056099},
{0, -14110899},
{-1045279, -14056099},
{-2079119, -13892399},
{-3090169, -13621500},
{-4067369, -13246399},
{-5000000, -12771100},
{-5877850, -12201100},
{-6691309, -11542300},
{-7431449, -10802200},
{-8090169, -9988750},
{-8660249, -9110899},
{-9135450, -8178270},
{-9510560, -7201069},
{-9781479, -6190020},
{-9945219, -5156179},
{-10000000, -4110899},
{-9945219, -3065619},
},
{
{-9945219, -3065619},
{-9781479, -2031780},
{-9510560, -1020730},
{-9135450, -43529},
{-2099999, 14110899},
{2099999, 14110899},
{9135450, -43529},
{9510560, -1020730},
{9781479, -2031780},
{9945219, -3065619},
{10000000, -4110899},
{9945219, -5156179},
{9781479, -6190020},
{9510560, -7201069},
{9135450, -8178270},
{8660249, -9110899},
{8090169, -9988750},
{7431449, -10802200},
{6691309, -11542300},
{5877850, -12201100},
{5000000, -12771100},
{4067369, -13246399},
{3090169, -13621500},
{2079119, -13892399},
{1045279, -14056099},
{0, -14110899},
{-1045279, -14056099},
{-2079119, -13892399},
{-3090169, -13621500},
{-4067369, -13246399},
{-5000000, -12771100},
{-5877850, -12201100},
{-6691309, -11542300},
{-7431449, -10802200},
{-8090169, -9988750},
{-8660249, -9110899},
{-9135450, -8178270},
{-9510560, -7201069},
{-9781479, -6190020},
{-9945219, -5156179},
{-10000000, -4110899},
{-9945219, -3065619},
},
{
{-9945219, -3065619},
{-9781479, -2031780},
{-9510560, -1020730},
{-9135450, -43529},
{-2099999, 14110899},
{2099999, 14110899},
{9135450, -43529},
{9510560, -1020730},
{9781479, -2031780},
{9945219, -3065619},
{10000000, -4110899},
{9945219, -5156179},
{9781479, -6190020},
{9510560, -7201069},
{9135450, -8178270},
{8660249, -9110899},
{8090169, -9988750},
{7431449, -10802200},
{6691309, -11542300},
{5877850, -12201100},
{5000000, -12771100},
{4067369, -13246399},
{3090169, -13621500},
{2079119, -13892399},
{1045279, -14056099},
{0, -14110899},
{-1045279, -14056099},
{-2079119, -13892399},
{-3090169, -13621500},
{-4067369, -13246399},
{-5000000, -12771100},
{-5877850, -12201100},
{-6691309, -11542300},
{-7431449, -10802200},
{-8090169, -9988750},
{-8660249, -9110899},
{-9135450, -8178270},
{-9510560, -7201069},
{-9781479, -6190020},
{-9945219, -5156179},
{-10000000, -4110899},
{-9945219, -3065619},
},
{
{-9945219, -3065619},
{-9781479, -2031780},
{-9510560, -1020730},
{-9135450, -43529},
{-2099999, 14110899},
{2099999, 14110899},
{9135450, -43529},
{9510560, -1020730},
{9781479, -2031780},
{9945219, -3065619},
{10000000, -4110899},
{9945219, -5156179},
{9781479, -6190020},
{9510560, -7201069},
{9135450, -8178270},
{8660249, -9110899},
{8090169, -9988750},
{7431449, -10802200},
{6691309, -11542300},
{5877850, -12201100},
{5000000, -12771100},
{4067369, -13246399},
{3090169, -13621500},
{2079119, -13892399},
{1045279, -14056099},
{0, -14110899},
{-1045279, -14056099},
{-2079119, -13892399},
{-3090169, -13621500},
{-4067369, -13246399},
{-5000000, -12771100},
{-5877850, -12201100},
{-6691309, -11542300},
{-7431449, -10802200},
{-8090169, -9988750},
{-8660249, -9110899},
{-9135450, -8178270},
{-9510560, -7201069},
{-9781479, -6190020},
{-9945219, -5156179},
{-10000000, -4110899},
{-9945219, -3065619},
},
{
{-18000000, -1000000},
{-15000000, 22000000},
{-11000000, 26000000},
{11000000, 26000000},
{15000000, 22000000},
{18000000, -1000000},
{18000000, -26000000},
{-18000000, -26000000},
{-18000000, -1000000},
},
};
std::vector<Item> proba = {
{
Rectangle(100, 2)
},
{
Rectangle(100, 2)
},
{
Rectangle(100, 2)
},
{
Rectangle(10, 10)
},
};
proba[0].rotate(Pi/3);
proba[1].rotate(Pi-Pi/3);
// std::vector<Item> input(25, Rectangle(70*SCALE, 10*SCALE));
std::vector<Item> input;
input.insert(input.end(), prusaParts().begin(), prusaParts().end());
// input.insert(input.end(), prusaExParts().begin(), prusaExParts().end());
// input.insert(input.end(), stegoParts().begin(), stegoParts().end());
// input.insert(input.end(), rects.begin(), rects.end());
// input.insert(input.end(), proba.begin(), proba.end());
// input.insert(input.end(), crasher.begin(), crasher.end());
Box bin(250*SCALE, 210*SCALE);
// PolygonImpl bin = {
@ -560,10 +121,12 @@ void arrangeRectangles() {
// {}
// };
auto min_obj_distance = static_cast<Coord>(0*SCALE);
// Circle bin({0, 0}, 125*SCALE);
using Placer = strategies::_NofitPolyPlacer<PolygonImpl, Box>;
using Packer = Arranger<Placer, FirstFitSelection>;
auto min_obj_distance = static_cast<Coord>(6*SCALE);
using Placer = placers::_NofitPolyPlacer<PolygonImpl, decltype(bin)>;
using Packer = Nester<Placer, FirstFitSelection>;
Packer arrange(bin, min_obj_distance);
@ -571,121 +134,23 @@ void arrangeRectangles() {
pconf.alignment = Placer::Config::Alignment::CENTER;
pconf.starting_point = Placer::Config::Alignment::CENTER;
pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/};
pconf.accuracy = 0.5f;
// auto bincenter = ShapeLike::boundingBox(bin).center();
// pconf.object_function = [&bin, bincenter](
// Placer::Pile pile, const Item& item,
// double /*area*/, double norm, double penality) {
// using pl = PointLike;
// static const double BIG_ITEM_TRESHOLD = 0.2;
// static const double GRAVITY_RATIO = 0.5;
// static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO;
// // We will treat big items (compared to the print bed) differently
// NfpPlacer::Pile bigs;
// bigs.reserve(pile.size());
// for(auto& p : pile) {
// auto pbb = ShapeLike::boundingBox(p);
// auto na = std::sqrt(pbb.width()*pbb.height())/norm;
// if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p);
// }
// // Candidate item bounding box
// auto ibb = item.boundingBox();
// // Calculate the full bounding box of the pile with the candidate item
// pile.emplace_back(item.transformedShape());
// auto fullbb = ShapeLike::boundingBox(pile);
// pile.pop_back();
// // The bounding box of the big items (they will accumulate in the center
// // of the pile
// auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs);
// // The size indicator of the candidate item. This is not the area,
// // but almost...
// auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm;
// // Will hold the resulting score
// double score = 0;
// if(itemnormarea > BIG_ITEM_TRESHOLD) {
// // This branch is for the bigger items..
// // Here we will use the closest point of the item bounding box to
// // the already arranged pile. So not the bb center nor the a choosen
// // corner but whichever is the closest to the center. This will
// // prevent unwanted strange arrangements.
// auto minc = ibb.minCorner(); // bottom left corner
// auto maxc = ibb.maxCorner(); // top right corner
// // top left and bottom right corners
// auto top_left = PointImpl{getX(minc), getY(maxc)};
// auto bottom_right = PointImpl{getX(maxc), getY(minc)};
// auto cc = fullbb.center(); // The gravity center
// // Now the distnce of the gravity center will be calculated to the
// // five anchor points and the smallest will be chosen.
// std::array<double, 5> dists;
// dists[0] = pl::distance(minc, cc);
// dists[1] = pl::distance(maxc, cc);
// dists[2] = pl::distance(ibb.center(), cc);
// dists[3] = pl::distance(top_left, cc);
// dists[4] = pl::distance(bottom_right, cc);
// auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
// // Density is the pack density: how big is the arranged pile
// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
// // The score is a weighted sum of the distance from pile center
// // and the pile size
// score = GRAVITY_RATIO * dist + DENSITY_RATIO * density;
// } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) {
// // If there are no big items, only small, we should consider the
// // density here as well to not get silly results
// auto bindist = pl::distance(ibb.center(), bincenter) / norm;
// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
// score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density;
// } else {
// // Here there are the small items that should be placed around the
// // already processed bigger items.
// // No need to play around with the anchor points, the center will be
// // just fine for small items
// score = pl::distance(ibb.center(), bigbb.center()) / norm;
// }
// // If it does not fit into the print bed we will beat it
// // with a large penality. If we would not do this, there would be only
// // one big pile that doesn't care whether it fits onto the print bed.
// if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score;
// return score;
// };
pconf.accuracy = 0.65f;
pconf.parallel = true;
Packer::SelectionConfig sconf;
// sconf.allow_parallel = false;
// sconf.force_parallel = false;
// sconf.try_triplets = true;
// sconf.try_reverse_order = true;
// sconf.waste_increment = 0.005;
// sconf.waste_increment = 0.01;
arrange.configure(pconf, sconf);
arrange.progressIndicator([&](unsigned r){
// svg::SVGWriter::Config conf;
// conf.mm_in_coord_units = SCALE;
// svg::SVGWriter svgw(conf);
// svgw.setSize(bin);
// svgw.writePackGroup(arrange.lastResult());
// svgw.save("debout");
std::cout << "Remaining items: " << r << std::endl;
})/*.useMinimumBoundigBoxRotation()*/;
});
// findMinimumBoundingBoxRotations(input.begin(), input.end());
Benchmark bench;
@ -693,7 +158,7 @@ void arrangeRectangles() {
Packer::ResultType result;
try {
result = arrange.arrange(input.begin(), input.end());
result = arrange.execute(input.begin(), input.end());
} catch(GeometryException& ge) {
std::cerr << "Geometry error: " << ge.what() << std::endl;
return ;
@ -707,7 +172,7 @@ void arrangeRectangles() {
std::vector<double> eff;
eff.reserve(result.size());
auto bin_area = ShapeLike::area<PolygonImpl>(bin);
auto bin_area = sl::area(bin);
for(auto& r : result) {
double a = 0;
std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); });
@ -728,10 +193,10 @@ void arrangeRectangles() {
for(auto& r : result) { std::cout << r.size() << " "; total += r.size(); }
std::cout << ") Total: " << total << std::endl;
for(auto& it : input) {
auto ret = ShapeLike::isValid(it.transformedShape());
std::cout << ret.second << std::endl;
}
// for(auto& it : input) {
// auto ret = sl::isValid(it.transformedShape());
// std::cout << ret.second << std::endl;
// }
if(total != input.size()) std::cout << "ERROR " << "could not pack "
<< input.size() - total << " elements!"
@ -744,13 +209,10 @@ void arrangeRectangles() {
SVGWriter svgw(conf);
svgw.setSize(Box(250*SCALE, 210*SCALE));
svgw.writePackGroup(result);
// std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);});
svgw.save("out");
}
int main(void /*int argc, char **argv*/) {
arrangeRectangles();
// findDegenerateCase();
return EXIT_SUCCESS;
}

View File

@ -22,6 +22,7 @@ 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>;
@ -29,15 +30,12 @@ using Rectangle = _Rectangle<PolygonImpl>;
using PackGroup = _PackGroup<PolygonImpl>;
using IndexedPackGroup = _IndexedPackGroup<PolygonImpl>;
using FillerSelection = strategies::_FillerSelection<PolygonImpl>;
using FirstFitSelection = strategies::_FirstFitSelection<PolygonImpl>;
using DJDHeuristic = strategies::_DJDHeuristic<PolygonImpl>;
using FillerSelection = selections::_FillerSelection<PolygonImpl>;
using FirstFitSelection = selections::_FirstFitSelection<PolygonImpl>;
using DJDHeuristic = selections::_DJDHeuristic<PolygonImpl>;
using NfpPlacer = strategies::_NofitPolyPlacer<PolygonImpl>;
using BottomLeftPlacer = strategies::_BottomLeftPlacer<PolygonImpl>;
//template<NfpLevel lvl = NfpLevel::BOTH_CONCAVE_WITH_HOLES>
//using NofitPolyPlacer = strategies::_NofitPolyPlacer<PolygonImpl, lvl>;
using NfpPlacer = placers::_NofitPolyPlacer<PolygonImpl>;
using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>;
}

View File

@ -36,7 +36,7 @@ using libnest2d::setX;
using libnest2d::setY;
using Box = libnest2d::_Box<PointImpl>;
using Segment = libnest2d::_Segment<PointImpl>;
using Shapes = libnest2d::Nfp::Shapes<PolygonImpl>;
using Shapes = libnest2d::nfp::Shapes<PolygonImpl>;
}
@ -241,11 +241,11 @@ template<> struct tag<bp2d::PolygonImpl> {
template<> struct exterior_ring<bp2d::PolygonImpl> {
static inline bp2d::PathImpl& get(bp2d::PolygonImpl& p) {
return libnest2d::ShapeLike::getContour(p);
return libnest2d::shapelike::getContour(p);
}
static inline bp2d::PathImpl const& get(bp2d::PolygonImpl const& p) {
return libnest2d::ShapeLike::getContour(p);
return libnest2d::shapelike::getContour(p);
}
};
@ -271,13 +271,13 @@ struct interior_rings<bp2d::PolygonImpl> {
static inline libnest2d::THolesContainer<bp2d::PolygonImpl>& get(
bp2d::PolygonImpl& p)
{
return libnest2d::ShapeLike::holes(p);
return libnest2d::shapelike::holes(p);
}
static inline const libnest2d::THolesContainer<bp2d::PolygonImpl>& get(
bp2d::PolygonImpl const& p)
{
return libnest2d::ShapeLike::holes(p);
return libnest2d::shapelike::holes(p);
}
};
@ -311,83 +311,77 @@ struct range_value<bp2d::Shapes> {
namespace libnest2d { // Now the algorithms that boost can provide...
namespace pointlike {
template<>
inline double PointLike::distance(const PointImpl& p1,
const PointImpl& p2 )
inline double distance(const PointImpl& p1, const PointImpl& p2 )
{
return boost::geometry::distance(p1, p2);
}
template<>
inline double PointLike::distance(const PointImpl& p,
const bp2d::Segment& seg )
inline double distance(const PointImpl& p, const bp2d::Segment& seg )
{
return boost::geometry::distance(p, seg);
}
}
namespace shapelike {
// Tell libnest2d how to make string out of a ClipperPolygon object
template<>
inline bool ShapeLike::intersects(const PathImpl& sh1,
const PathImpl& sh2)
inline bool intersects(const PathImpl& sh1, const PathImpl& sh2)
{
return boost::geometry::intersects(sh1, sh2);
}
// Tell libnest2d how to make string out of a ClipperPolygon object
template<>
inline bool ShapeLike::intersects(const PolygonImpl& sh1,
const PolygonImpl& sh2)
inline bool intersects(const PolygonImpl& sh1, const PolygonImpl& sh2)
{
return boost::geometry::intersects(sh1, sh2);
}
// Tell libnest2d how to make string out of a ClipperPolygon object
template<>
inline bool ShapeLike::intersects(const bp2d::Segment& s1,
const bp2d::Segment& s2)
inline bool intersects(const bp2d::Segment& s1, const bp2d::Segment& s2)
{
return boost::geometry::intersects(s1, s2);
}
#ifndef DISABLE_BOOST_AREA
template<>
inline double ShapeLike::area(const PolygonImpl& shape)
inline double area(const PolygonImpl& shape, const PolygonTag&)
{
return boost::geometry::area(shape);
}
#endif
template<>
inline bool ShapeLike::isInside<PolygonImpl>(const PointImpl& point,
const PolygonImpl& shape)
inline bool isInside(const PointImpl& point, const PolygonImpl& shape)
{
return boost::geometry::within(point, shape);
}
template<>
inline bool ShapeLike::isInside(const PolygonImpl& sh1,
const PolygonImpl& sh2)
inline bool isInside(const PolygonImpl& sh1, const PolygonImpl& sh2)
{
return boost::geometry::within(sh1, sh2);
}
template<>
inline bool ShapeLike::touches( const PolygonImpl& sh1,
const PolygonImpl& sh2)
inline bool touches(const PolygonImpl& sh1, const PolygonImpl& sh2)
{
return boost::geometry::touches(sh1, sh2);
}
template<>
inline bool ShapeLike::touches( const PointImpl& point,
const PolygonImpl& shape)
inline bool touches( const PointImpl& point, const PolygonImpl& shape)
{
return boost::geometry::touches(point, shape);
}
#ifndef DISABLE_BOOST_BOUNDING_BOX
template<>
inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh)
inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&)
{
bp2d::Box b;
boost::geometry::envelope(sh, b);
@ -395,7 +389,8 @@ inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh)
}
template<>
inline bp2d::Box ShapeLike::boundingBox<PolygonImpl>(const bp2d::Shapes& shapes)
inline bp2d::Box boundingBox<bp2d::Shapes>(const bp2d::Shapes& shapes,
const MultiPolygonTag&)
{
bp2d::Box b;
boost::geometry::envelope(shapes, b);
@ -405,7 +400,7 @@ inline bp2d::Box ShapeLike::boundingBox<PolygonImpl>(const bp2d::Shapes& shapes)
#ifndef DISABLE_BOOST_CONVEX_HULL
template<>
inline PolygonImpl ShapeLike::convexHull(const PolygonImpl& sh)
inline PolygonImpl convexHull(const PolygonImpl& sh, const PolygonTag&)
{
PolygonImpl ret;
boost::geometry::convex_hull(sh, ret);
@ -413,7 +408,8 @@ inline PolygonImpl ShapeLike::convexHull(const PolygonImpl& sh)
}
template<>
inline PolygonImpl ShapeLike::convexHull(const bp2d::Shapes& shapes)
inline PolygonImpl convexHull(const TMultiShape<PolygonImpl>& shapes,
const MultiPolygonTag&)
{
PolygonImpl ret;
boost::geometry::convex_hull(shapes, ret);
@ -421,56 +417,17 @@ inline PolygonImpl ShapeLike::convexHull(const bp2d::Shapes& shapes)
}
#endif
#ifndef DISABLE_BOOST_ROTATE
template<>
inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads)
{
namespace trans = boost::geometry::strategy::transform;
PolygonImpl cpy = sh;
trans::rotate_transformer<boost::geometry::radian, Radians, 2, 2>
rotate(rads);
boost::geometry::transform(cpy, sh, rotate);
}
#endif
#ifndef DISABLE_BOOST_TRANSLATE
template<>
inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs)
{
namespace trans = boost::geometry::strategy::transform;
PolygonImpl cpy = sh;
trans::translate_transformer<bp2d::Coord, 2, 2> translate(
bp2d::getX(offs), bp2d::getY(offs));
boost::geometry::transform(cpy, sh, translate);
}
#endif
#ifndef DISABLE_BOOST_OFFSET
template<>
inline void ShapeLike::offset(PolygonImpl& sh, bp2d::Coord distance)
inline void offset(PolygonImpl& sh, bp2d::Coord distance)
{
PolygonImpl cpy = sh;
boost::geometry::buffer(cpy, sh, distance);
}
#endif
#ifndef DISABLE_BOOST_NFP_MERGE
template<>
inline bp2d::Shapes Nfp::merge(const bp2d::Shapes& shapes,
const PolygonImpl& sh)
{
bp2d::Shapes retv;
boost::geometry::union_(shapes, sh, retv);
return retv;
}
#endif
#ifndef DISABLE_BOOST_SERIALIZE
template<> inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>(
template<> inline std::string serialize<libnest2d::Formats::SVG>(
const PolygonImpl& sh, double scale)
{
std::stringstream ss;
@ -482,14 +439,14 @@ template<> inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>(
Polygonf::ring_type ring;
Polygonf::inner_container_type holes;
ring.reserve(ShapeLike::contourVertexCount(sh));
ring.reserve(shapelike::contourVertexCount(sh));
for(auto it = ShapeLike::cbegin(sh); it != ShapeLike::cend(sh); it++) {
for(auto it = shapelike::cbegin(sh); it != shapelike::cend(sh); it++) {
auto& v = *it;
ring.emplace_back(getX(v)*scale, getY(v)*scale);
};
auto H = ShapeLike::holes(sh);
auto H = shapelike::holes(sh);
for(PathImpl& h : H ) {
Polygonf::ring_type hf;
for(auto it = h.begin(); it != h.end(); it++) {
@ -512,21 +469,47 @@ template<> inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>(
#ifndef DISABLE_BOOST_UNSERIALIZE
template<>
inline void ShapeLike::unserialize<libnest2d::Formats::SVG>(
inline void unserialize<libnest2d::Formats::SVG>(
PolygonImpl& sh,
const std::string& str)
{
}
#endif
template<> inline std::pair<bool, std::string>
ShapeLike::isValid(const PolygonImpl& sh)
template<> inline std::pair<bool, std::string> isValid(const PolygonImpl& sh)
{
std::string message;
bool ret = boost::geometry::is_valid(sh, message);
return {ret, message};
}
}
namespace nfp {
#ifndef DISABLE_BOOST_NFP_MERGE
// Warning: I could not get boost union_ to work. Geometries will overlap.
template<>
inline bp2d::Shapes nfp::merge(const bp2d::Shapes& shapes,
const PolygonImpl& sh)
{
bp2d::Shapes retv;
boost::geometry::union_(shapes, sh, retv);
return retv;
}
template<>
inline bp2d::Shapes nfp::merge(const bp2d::Shapes& shapes)
{
bp2d::Shapes retv;
boost::geometry::union_(shapes, shapes.back(), retv);
return retv;
}
#endif
}
}

View File

@ -99,49 +99,54 @@ template<> struct PointType<PolygonImpl> {
using Type = PointImpl;
};
// Type of vertex iterator used by Clipper
template<> struct VertexIteratorType<PolygonImpl> {
using Type = ClipperLib::Path::iterator;
};
// Type of vertex iterator used by Clipper
template<> struct VertexConstIteratorType<PolygonImpl> {
using Type = ClipperLib::Path::const_iterator;
template<> struct PointType<PointImpl> {
using Type = PointImpl;
};
template<> struct CountourType<PolygonImpl> {
using Type = PathImpl;
};
// Tell binpack2d how to extract the X coord from a ClipperPoint object
template<> inline TCoord<PointImpl> PointLike::x(const PointImpl& p)
template<> struct ShapeTag<PolygonImpl> { using Type = PolygonTag; };
template<> struct ShapeTag<TMultiShape<PolygonImpl>> {
using Type = MultiPolygonTag;
};
template<> struct PointType<TMultiShape<PolygonImpl>> {
using Type = PointImpl;
};
template<> struct HolesContainer<PolygonImpl> {
using Type = ClipperLib::Paths;
};
namespace pointlike {
// Tell libnest2d how to extract the X coord from a ClipperPoint object
template<> inline TCoord<PointImpl> x(const PointImpl& p)
{
return p.X;
}
// Tell binpack2d how to extract the Y coord from a ClipperPoint object
template<> inline TCoord<PointImpl> PointLike::y(const PointImpl& p)
// Tell libnest2d how to extract the Y coord from a ClipperPoint object
template<> inline TCoord<PointImpl> y(const PointImpl& p)
{
return p.Y;
}
// Tell binpack2d how to extract the X coord from a ClipperPoint object
template<> inline TCoord<PointImpl>& PointLike::x(PointImpl& p)
// Tell libnest2d how to extract the X coord from a ClipperPoint object
template<> inline TCoord<PointImpl>& x(PointImpl& p)
{
return p.X;
}
// Tell binpack2d how to extract the Y coord from a ClipperPoint object
template<>
inline TCoord<PointImpl>& PointLike::y(PointImpl& p)
// Tell libnest2d how to extract the Y coord from a ClipperPoint object
template<> inline TCoord<PointImpl>& y(PointImpl& p)
{
return p.Y;
}
template<>
inline void ShapeLike::reserve(PolygonImpl& sh, size_t vertex_capacity)
{
return sh.Contour.reserve(vertex_capacity);
}
#define DISABLE_BOOST_AREA
@ -175,16 +180,24 @@ inline double area<Orientation::COUNTER_CLOCKWISE>(const PolygonImpl& sh) {
return ClipperLib::Area(sh.Contour) + a;
}
}
// Tell binpack2d how to make string out of a ClipperPolygon object
template<>
inline double ShapeLike::area(const PolygonImpl& sh) {
namespace shapelike {
template<> inline void reserve(PolygonImpl& sh, size_t vertex_capacity)
{
return sh.Contour.reserve(vertex_capacity);
}
// Tell libnest2d how to make string out of a ClipperPolygon object
template<> inline double area(const PolygonImpl& sh, const PolygonTag&)
{
return _smartarea::area<OrientationType<PolygonImpl>::Value>(sh);
}
template<>
inline void ShapeLike::offset(PolygonImpl& sh, TCoord<PointImpl> distance) {
template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance)
{
#define DISABLE_BOOST_OFFSET
using ClipperLib::ClipperOffset;
@ -234,7 +247,8 @@ inline void ShapeLike::offset(PolygonImpl& sh, TCoord<PointImpl> distance) {
}
// Tell libnest2d how to make string out of a ClipperPolygon object
template<> inline std::string ShapeLike::toString(const PolygonImpl& sh) {
template<> inline std::string toString(const PolygonImpl& sh)
{
std::stringstream ss;
ss << "Contour {\n";
@ -257,37 +271,8 @@ template<> inline std::string ShapeLike::toString(const PolygonImpl& sh) {
}
template<>
inline TVertexIterator<PolygonImpl> ShapeLike::begin(PolygonImpl& sh)
inline PolygonImpl create(const PathImpl& path, const HoleStore& holes)
{
return sh.Contour.begin();
}
template<>
inline TVertexIterator<PolygonImpl> ShapeLike::end(PolygonImpl& sh)
{
return sh.Contour.end();
}
template<>
inline TVertexConstIterator<PolygonImpl> ShapeLike::cbegin(
const PolygonImpl& sh)
{
return sh.Contour.cbegin();
}
template<>
inline TVertexConstIterator<PolygonImpl> ShapeLike::cend(
const PolygonImpl& sh)
{
return sh.Contour.cend();
}
template<> struct HolesContainer<PolygonImpl> {
using Type = ClipperLib::Paths;
};
template<> inline PolygonImpl ShapeLike::create(const PathImpl& path,
const HoleStore& holes) {
PolygonImpl p;
p.Contour = path;
@ -308,8 +293,7 @@ template<> inline PolygonImpl ShapeLike::create(const PathImpl& path,
return p;
}
template<> inline PolygonImpl ShapeLike::create( PathImpl&& path,
HoleStore&& holes) {
template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) {
PolygonImpl p;
p.Contour.swap(path);
@ -331,49 +315,49 @@ template<> inline PolygonImpl ShapeLike::create( PathImpl&& path,
return p;
}
template<> inline const THolesContainer<PolygonImpl>&
ShapeLike::holes(const PolygonImpl& sh)
template<>
inline const THolesContainer<PolygonImpl>& holes(const PolygonImpl& sh)
{
return sh.Holes;
}
template<> inline THolesContainer<PolygonImpl>&
ShapeLike::holes(PolygonImpl& sh)
template<> inline THolesContainer<PolygonImpl>& holes(PolygonImpl& sh)
{
return sh.Holes;
}
template<> inline TContour<PolygonImpl>&
ShapeLike::getHole(PolygonImpl& sh, unsigned long idx)
template<>
inline TContour<PolygonImpl>& getHole(PolygonImpl& sh, unsigned long idx)
{
return sh.Holes[idx];
}
template<> inline const TContour<PolygonImpl>&
ShapeLike::getHole(const PolygonImpl& sh, unsigned long idx)
template<>
inline const TContour<PolygonImpl>& getHole(const PolygonImpl& sh,
unsigned long idx)
{
return sh.Holes[idx];
}
template<> inline size_t ShapeLike::holeCount(const PolygonImpl& sh)
template<> inline size_t holeCount(const PolygonImpl& sh)
{
return sh.Holes.size();
}
template<> inline PathImpl& ShapeLike::getContour(PolygonImpl& sh)
template<> inline PathImpl& getContour(PolygonImpl& sh)
{
return sh.Contour;
}
template<>
inline const PathImpl& ShapeLike::getContour(const PolygonImpl& sh)
inline const PathImpl& getContour(const PolygonImpl& sh)
{
return sh.Contour;
}
#define DISABLE_BOOST_TRANSLATE
template<>
inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs)
inline void translate(PolygonImpl& sh, const PointImpl& offs)
{
for(auto& p : sh.Contour) { p += offs; }
for(auto& hole : sh.Holes) for(auto& p : hole) { p += offs; }
@ -381,7 +365,7 @@ inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs)
#define DISABLE_BOOST_ROTATE
template<>
inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads)
inline void rotate(PolygonImpl& sh, const Radians& rads)
{
using Coord = TCoord<PointImpl>;
@ -402,9 +386,11 @@ inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads)
}
}
} // namespace shapelike
#define DISABLE_BOOST_NFP_MERGE
inline Nfp::Shapes<PolygonImpl> _merge(ClipperLib::Clipper& clipper) {
Nfp::Shapes<PolygonImpl> retv;
inline std::vector<PolygonImpl> _merge(ClipperLib::Clipper& clipper) {
shapelike::Shapes<PolygonImpl> retv;
ClipperLib::PolyTree result;
clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative);
@ -438,8 +424,10 @@ inline Nfp::Shapes<PolygonImpl> _merge(ClipperLib::Clipper& clipper) {
return retv;
}
template<> inline Nfp::Shapes<PolygonImpl>
Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes)
namespace nfp {
template<> inline std::vector<PolygonImpl>
merge(const std::vector<PolygonImpl>& shapes)
{
ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);
@ -461,6 +449,8 @@ Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes)
}
}
//#define DISABLE_BOOST_SERIALIZE
//#define DISABLE_BOOST_UNSERIALIZE

View File

@ -22,34 +22,12 @@ template<class GeomType>
using TCoord = typename CoordType<remove_cvref_t<GeomType>>::Type;
/// Getting the type of point structure used by a shape.
template<class Shape> struct PointType { /*using Type = void;*/ };
template<class Sh> struct PointType { using Type = typename Sh::PointType; };
/// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
template<class Shape>
using TPoint = typename PointType<remove_cvref_t<Shape>>::Type;
/// Getting the VertexIterator type of a shape class.
template<class Shape> struct VertexIteratorType { /*using Type = void;*/ };
/// Getting the const vertex iterator for a shape class.
template<class Shape> struct VertexConstIteratorType {/* using Type = void;*/ };
/**
* TVertexIterator<Shape> as shorthand for
* `typename VertexIteratorType<Shape>::Type`
*/
template<class Shape>
using TVertexIterator =
typename VertexIteratorType<remove_cvref_t<Shape>>::Type;
/**
* \brief TVertexConstIterator<Shape> as shorthand for
* `typename VertexConstIteratorType<Shape>::Type`
*/
template<class ShapeClass>
using TVertexConstIterator =
typename VertexConstIteratorType<remove_cvref_t<ShapeClass>>::Type;
/**
* \brief A point pair base class for other point pairs (segment, box, ...).
* \tparam RawPoint The actual point type to use.
@ -60,6 +38,17 @@ struct PointPair {
RawPoint p2;
};
struct PolygonTag {};
struct MultiPolygonTag {};
struct BoxTag {};
struct CircleTag {};
template<class Shape> struct ShapeTag { using Type = typename Shape::Tag; };
template<class S> using Tag = typename ShapeTag<S>::Type;
template<class S> struct MultiShape { using Type = std::vector<S>; };
template<class S> using TMultiShape = typename MultiShape<S>::Type;
/**
* \brief An abstraction of a box;
*/
@ -69,6 +58,9 @@ class _Box: PointPair<RawPoint> {
using PointPair<RawPoint>::p2;
public:
using Tag = BoxTag;
using PointType = RawPoint;
inline _Box() = default;
inline _Box(const RawPoint& p, const RawPoint& pp):
PointPair<RawPoint>({p, pp}) {}
@ -98,6 +90,9 @@ class _Circle {
double radius_ = 0;
public:
using Tag = CircleTag;
using PointType = RawPoint;
_Circle() = default;
_Circle(const RawPoint& center, double r): center_(center), radius_(r) {}
@ -109,7 +104,7 @@ public:
inline void radius(double r) { radius_ = r; }
inline double area() const BP2D_NOEXCEPT {
return 2.0*Pi*radius_;
return 2.0*Pi*radius_*radius_;
}
};
@ -123,6 +118,8 @@ class _Segment: PointPair<RawPoint> {
mutable Radians angletox_ = std::nan("");
public:
using PointType = RawPoint;
inline _Segment() = default;
inline _Segment(const RawPoint& p, const RawPoint& pp):
@ -156,36 +153,36 @@ public:
inline double length();
};
// This struct serves as a namespace. The only difference is that is can be
// This struct serves almost as a namespace. The only difference is that is can
// used in friend declarations.
struct PointLike {
namespace pointlike {
template<class RawPoint>
static TCoord<RawPoint> x(const RawPoint& p)
inline TCoord<RawPoint> x(const RawPoint& p)
{
return p(0);
}
template<class RawPoint>
static TCoord<RawPoint> y(const RawPoint& p)
inline TCoord<RawPoint> y(const RawPoint& p)
{
return p(1);
}
template<class RawPoint>
static TCoord<RawPoint>& x(RawPoint& p)
inline TCoord<RawPoint>& x(RawPoint& p)
{
return p(0);
}
template<class RawPoint>
static TCoord<RawPoint>& y(RawPoint& p)
inline TCoord<RawPoint>& y(RawPoint& p)
{
return p(1);
}
template<class RawPoint>
static double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/)
inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/)
{
static_assert(always_false<RawPoint>::value,
"PointLike::distance(point, point) unimplemented!");
@ -193,7 +190,7 @@ struct PointLike {
}
template<class RawPoint>
static double distance(const RawPoint& /*p1*/,
inline double distance(const RawPoint& /*p1*/,
const _Segment<RawPoint>& /*s*/)
{
static_assert(always_false<RawPoint>::value,
@ -202,13 +199,13 @@ struct PointLike {
}
template<class RawPoint>
static std::pair<TCoord<RawPoint>, bool> horizontalDistance(
inline std::pair<TCoord<RawPoint>, bool> horizontalDistance(
const RawPoint& p, const _Segment<RawPoint>& s)
{
using Unit = TCoord<RawPoint>;
auto x = PointLike::x(p), y = PointLike::y(p);
auto x1 = PointLike::x(s.first()), y1 = PointLike::y(s.first());
auto x2 = PointLike::x(s.second()), y2 = PointLike::y(s.second());
auto x = pointlike::x(p), y = pointlike::y(p);
auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first());
auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second());
TCoord<RawPoint> ret;
@ -228,13 +225,13 @@ struct PointLike {
}
template<class RawPoint>
static std::pair<TCoord<RawPoint>, bool> verticalDistance(
inline std::pair<TCoord<RawPoint>, bool> verticalDistance(
const RawPoint& p, const _Segment<RawPoint>& s)
{
using Unit = TCoord<RawPoint>;
auto x = PointLike::x(p), y = PointLike::y(p);
auto x1 = PointLike::x(s.first()), y1 = PointLike::y(s.first());
auto x2 = PointLike::x(s.second()), y2 = PointLike::y(s.second());
auto x = pointlike::x(p), y = pointlike::y(p);
auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first());
auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second());
TCoord<RawPoint> ret;
@ -252,36 +249,36 @@ struct PointLike {
return {ret, true};
}
};
}
template<class RawPoint>
TCoord<RawPoint> _Box<RawPoint>::width() const BP2D_NOEXCEPT
{
return PointLike::x(maxCorner()) - PointLike::x(minCorner());
return pointlike::x(maxCorner()) - pointlike::x(minCorner());
}
template<class RawPoint>
TCoord<RawPoint> _Box<RawPoint>::height() const BP2D_NOEXCEPT
{
return PointLike::y(maxCorner()) - PointLike::y(minCorner());
return pointlike::y(maxCorner()) - pointlike::y(minCorner());
}
template<class RawPoint>
TCoord<RawPoint> getX(const RawPoint& p) { return PointLike::x<RawPoint>(p); }
TCoord<RawPoint> getX(const RawPoint& p) { return pointlike::x<RawPoint>(p); }
template<class RawPoint>
TCoord<RawPoint> getY(const RawPoint& p) { return PointLike::y<RawPoint>(p); }
TCoord<RawPoint> getY(const RawPoint& p) { return pointlike::y<RawPoint>(p); }
template<class RawPoint>
void setX(RawPoint& p, const TCoord<RawPoint>& val)
{
PointLike::x<RawPoint>(p) = val;
pointlike::x<RawPoint>(p) = val;
}
template<class RawPoint>
void setY(RawPoint& p, const TCoord<RawPoint>& val)
{
PointLike::y<RawPoint>(p) = val;
pointlike::y<RawPoint>(p) = val;
}
template<class RawPoint>
@ -303,7 +300,7 @@ inline Radians _Segment<RawPoint>::angleToXaxis() const
template<class RawPoint>
inline double _Segment<RawPoint>::length()
{
return PointLike::distance(first(), second());
return pointlike::distance(first(), second());
}
template<class RawPoint>
@ -313,9 +310,9 @@ inline RawPoint _Box<RawPoint>::center() const BP2D_NOEXCEPT {
using Coord = TCoord<RawPoint>;
RawPoint ret = {
static_cast<Coord>( std::round((getX(minc) + getX(maxc))/2.0) ),
static_cast<Coord>( std::round((getY(minc) + getY(maxc))/2.0) )
RawPoint ret = { // No rounding here, we dont know if these are int coords
static_cast<Coord>( (getX(minc) + getX(maxc))/2.0 ),
static_cast<Coord>( (getY(minc) + getY(maxc))/2.0 )
};
return ret;
@ -356,124 +353,125 @@ enum class Formats {
// This struct serves as a namespace. The only difference is that it can be
// used in friend declarations and can be aliased at class scope.
struct ShapeLike {
namespace shapelike {
template<class RawShape>
using Shapes = std::vector<RawShape>;
using Shapes = TMultiShape<RawShape>;
template<class RawShape>
static RawShape create(const TContour<RawShape>& contour,
inline RawShape create(const TContour<RawShape>& contour,
const THolesContainer<RawShape>& holes)
{
return RawShape(contour, holes);
}
template<class RawShape>
static RawShape create(TContour<RawShape>&& contour,
inline RawShape create(TContour<RawShape>&& contour,
THolesContainer<RawShape>&& holes)
{
return RawShape(contour, holes);
}
template<class RawShape>
static RawShape create(const TContour<RawShape>& contour)
inline RawShape create(const TContour<RawShape>& contour)
{
return create<RawShape>(contour, {});
}
template<class RawShape>
static RawShape create(TContour<RawShape>&& contour)
inline RawShape create(TContour<RawShape>&& contour)
{
return create<RawShape>(contour, {});
}
template<class RawShape>
static THolesContainer<RawShape>& holes(RawShape& /*sh*/)
inline THolesContainer<RawShape>& holes(RawShape& /*sh*/)
{
static THolesContainer<RawShape> empty;
return empty;
}
template<class RawShape>
static const THolesContainer<RawShape>& holes(const RawShape& /*sh*/)
inline const THolesContainer<RawShape>& holes(const RawShape& /*sh*/)
{
static THolesContainer<RawShape> empty;
return empty;
}
template<class RawShape>
static TContour<RawShape>& getHole(RawShape& sh, unsigned long idx)
inline TContour<RawShape>& getHole(RawShape& sh, unsigned long idx)
{
return holes(sh)[idx];
}
template<class RawShape>
static const TContour<RawShape>& getHole(const RawShape& sh,
inline const TContour<RawShape>& getHole(const RawShape& sh,
unsigned long idx)
{
return holes(sh)[idx];
}
template<class RawShape>
static size_t holeCount(const RawShape& sh)
inline size_t holeCount(const RawShape& sh)
{
return holes(sh).size();
}
template<class RawShape>
static TContour<RawShape>& getContour(RawShape& sh)
inline TContour<RawShape>& getContour(RawShape& sh)
{
return sh;
}
template<class RawShape>
static const TContour<RawShape>& getContour(const RawShape& sh)
inline const TContour<RawShape>& getContour(const RawShape& sh)
{
return sh;
}
// Optional, does nothing by default
template<class RawShape>
static void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {}
inline void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {}
template<class RawShape, class...Args>
static void addVertex(RawShape& sh, Args...args)
inline void addVertex(RawShape& sh, Args...args)
{
return getContour(sh).emplace_back(std::forward<Args>(args)...);
}
template<class RawShape>
static TVertexIterator<RawShape> begin(RawShape& sh)
inline typename TContour<RawShape>::iterator begin(RawShape& sh)
{
return sh.begin();
return getContour(sh).begin();
}
template<class RawShape>
static TVertexIterator<RawShape> end(RawShape& sh)
inline typename TContour<RawShape>::iterator end(RawShape& sh)
{
return sh.end();
return getContour(sh).end();
}
template<class RawShape>
static TVertexConstIterator<RawShape> cbegin(const RawShape& sh)
inline typename TContour<RawShape>::const_iterator
cbegin(const RawShape& sh)
{
return sh.cbegin();
return getContour(sh).cbegin();
}
template<class RawShape>
static TVertexConstIterator<RawShape> cend(const RawShape& sh)
inline typename TContour<RawShape>::const_iterator cend(const RawShape& sh)
{
return sh.cend();
return getContour(sh).cend();
}
template<class RawShape>
static std::string toString(const RawShape& /*sh*/)
inline std::string toString(const RawShape& /*sh*/)
{
return "";
}
template<Formats, class RawShape>
static std::string serialize(const RawShape& /*sh*/, double /*scale*/=1)
inline std::string serialize(const RawShape& /*sh*/, double /*scale*/=1)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::serialize() unimplemented!");
@ -481,14 +479,14 @@ struct ShapeLike {
}
template<Formats, class RawShape>
static void unserialize(RawShape& /*sh*/, const std::string& /*str*/)
inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::unserialize() unimplemented!");
}
template<class RawShape>
static double area(const RawShape& /*sh*/)
inline double area(const RawShape& /*sh*/, const PolygonTag&)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::area() unimplemented!");
@ -496,7 +494,7 @@ struct ShapeLike {
}
template<class RawShape>
static bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/)
inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::intersects() unimplemented!");
@ -504,7 +502,7 @@ struct ShapeLike {
}
template<class RawShape>
static bool isInside(const TPoint<RawShape>& /*point*/,
inline bool isInside(const TPoint<RawShape>& /*point*/,
const RawShape& /*shape*/)
{
static_assert(always_false<RawShape>::value,
@ -513,7 +511,7 @@ struct ShapeLike {
}
template<class RawShape>
static bool isInside(const RawShape& /*shape*/,
inline bool isInside(const RawShape& /*shape*/,
const RawShape& /*shape*/)
{
static_assert(always_false<RawShape>::value,
@ -522,7 +520,7 @@ struct ShapeLike {
}
template<class RawShape>
static bool touches( const RawShape& /*shape*/,
inline bool touches( const RawShape& /*shape*/,
const RawShape& /*shape*/)
{
static_assert(always_false<RawShape>::value,
@ -531,7 +529,7 @@ struct ShapeLike {
}
template<class RawShape>
static bool touches( const TPoint<RawShape>& /*point*/,
inline bool touches( const TPoint<RawShape>& /*point*/,
const RawShape& /*shape*/)
{
static_assert(always_false<RawShape>::value,
@ -540,64 +538,66 @@ struct ShapeLike {
}
template<class RawShape>
static _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/)
inline _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/,
const PolygonTag&)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::boundingBox(shape) unimplemented!");
}
template<class RawShape>
static _Box<TPoint<RawShape>> boundingBox(const Shapes<RawShape>& /*sh*/)
template<class RawShapes>
inline _Box<TPoint<typename RawShapes::value_type>>
boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&)
{
static_assert(always_false<RawShape>::value,
static_assert(always_false<RawShapes>::value,
"ShapeLike::boundingBox(shapes) unimplemented!");
}
template<class RawShape>
static RawShape convexHull(const RawShape& /*sh*/)
inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::convexHull(shape) unimplemented!");
return RawShape();
}
template<class RawShape>
static RawShape convexHull(const Shapes<RawShape>& /*sh*/)
template<class RawShapes>
inline typename RawShapes::value_type
convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&)
{
static_assert(always_false<RawShape>::value,
static_assert(always_false<RawShapes>::value,
"ShapeLike::convexHull(shapes) unimplemented!");
return RawShape();
return typename RawShapes::value_type();
}
template<class RawShape>
static void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::rotate() unimplemented!");
}
template<class RawShape, class RawPoint>
static void translate(RawShape& /*sh*/, const RawPoint& /*offs*/)
inline void translate(RawShape& /*sh*/, const RawPoint& /*offs*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::translate() unimplemented!");
}
template<class RawShape>
static void offset(RawShape& /*sh*/, TCoord<TPoint<RawShape>> /*distance*/)
inline void offset(RawShape& /*sh*/, TCoord<TPoint<RawShape>> /*distance*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::offset() unimplemented!");
dout() << "The current geometry backend does not support offsetting!\n";
}
template<class RawShape>
static std::pair<bool, std::string> isValid(const RawShape& /*sh*/)
inline std::pair<bool, std::string> isValid(const RawShape& /*sh*/)
{
return {false, "ShapeLike::isValid() unimplemented!"};
}
template<class RawShape>
static inline bool isConvex(const TContour<RawShape>& sh)
inline bool isConvex(const TContour<RawShape>& sh)
{
using Vertex = TPoint<RawShape>;
auto first = sh.begin();
@ -633,43 +633,55 @@ struct ShapeLike {
// No need to implement these
// *************************************************************************
template<class RawShape>
static inline _Box<TPoint<RawShape>> boundingBox(
const _Box<TPoint<RawShape>>& box)
template<class Box>
inline Box boundingBox(const Box& box, const BoxTag& )
{
return box;
}
template<class RawShape>
static inline _Box<TPoint<RawShape>> boundingBox(
const _Circle<TPoint<RawShape>>& circ)
template<class Circle>
inline _Box<typename Circle::PointType> boundingBox(
const Circle& circ, const CircleTag&)
{
using Coord = TCoord<TPoint<RawShape>>;
TPoint<RawShape> pmin = {
using Point = typename Circle::PointType;
using Coord = TCoord<Point>;
Point pmin = {
static_cast<Coord>(getX(circ.center()) - circ.radius()),
static_cast<Coord>(getY(circ.center()) - circ.radius()) };
TPoint<RawShape> pmax = {
Point pmax = {
static_cast<Coord>(getX(circ.center()) + circ.radius()),
static_cast<Coord>(getY(circ.center()) + circ.radius()) };
return {pmin, pmax};
}
template<class RawShape>
static inline double area(const _Box<TPoint<RawShape>>& box)
template<class S> // Dispatch function
inline _Box<TPoint<S>> boundingBox(const S& sh)
{
return static_cast<double>(box.width() * box.height());
return boundingBox(sh, Tag<S>() );
}
template<class RawShape>
static inline double area(const _Circle<TPoint<RawShape>>& circ)
template<class Box>
inline double area(const Box& box, const BoxTag& )
{
return box.area();
}
template<class Circle>
inline double area(const Circle& circ, const CircleTag& )
{
return circ.area();
}
template<class RawShape> // Dispatching function
inline double area(const RawShape& sh)
{
return area(sh, Tag<RawShape>());
}
template<class RawShape>
static inline double area(const Shapes<RawShape>& shapes)
inline double area(const Shapes<RawShape>& shapes)
{
return std::accumulate(shapes.begin(), shapes.end(), 0.0,
[](double a, const RawShape& b) {
@ -678,14 +690,21 @@ struct ShapeLike {
}
template<class RawShape>
static bool isInside(const TPoint<RawShape>& point,
const _Circle<TPoint<RawShape>>& circ)
inline auto convexHull(const RawShape& sh)
-> decltype(convexHull(sh, Tag<RawShape>())) // TODO: C++14 could deduce
{
return PointLike::distance(point, circ.center()) < circ.radius();
return convexHull(sh, Tag<RawShape>());
}
template<class RawShape>
static bool isInside(const TPoint<RawShape>& point,
inline bool isInside(const TPoint<RawShape>& point,
const _Circle<TPoint<RawShape>>& circ)
{
return pointlike::distance(point, circ.center()) < circ.radius();
}
template<class RawShape>
inline bool isInside(const TPoint<RawShape>& point,
const _Box<TPoint<RawShape>>& box)
{
auto px = getX(point);
@ -699,7 +718,7 @@ struct ShapeLike {
}
template<class RawShape>
static bool isInside(const RawShape& sh,
inline bool isInside(const RawShape& sh,
const _Circle<TPoint<RawShape>>& circ)
{
return std::all_of(cbegin(sh), cend(sh),
@ -709,7 +728,7 @@ struct ShapeLike {
}
template<class RawShape>
static bool isInside(const _Box<TPoint<RawShape>>& box,
inline bool isInside(const _Box<TPoint<RawShape>>& box,
const _Circle<TPoint<RawShape>>& circ)
{
return isInside<RawShape>(box.minCorner(), circ) &&
@ -717,7 +736,7 @@ struct ShapeLike {
}
template<class RawShape>
static bool isInside(const _Box<TPoint<RawShape>>& ibb,
inline bool isInside(const _Box<TPoint<RawShape>>& ibb,
const _Box<TPoint<RawShape>>& box)
{
auto iminX = getX(ibb.minCorner());
@ -734,31 +753,31 @@ struct ShapeLike {
}
template<class RawShape> // Potential O(1) implementation may exist
static inline TPoint<RawShape>& vertex(RawShape& sh, unsigned long idx)
inline TPoint<RawShape>& vertex(RawShape& sh, unsigned long idx)
{
return *(begin(sh) + idx);
}
template<class RawShape> // Potential O(1) implementation may exist
static inline const TPoint<RawShape>& vertex(const RawShape& sh,
inline const TPoint<RawShape>& vertex(const RawShape& sh,
unsigned long idx)
{
return *(cbegin(sh) + idx);
}
template<class RawShape>
static inline size_t contourVertexCount(const RawShape& sh)
inline size_t contourVertexCount(const RawShape& sh)
{
return cend(sh) - cbegin(sh);
}
template<class RawShape, class Fn>
static inline void foreachContourVertex(RawShape& sh, Fn fn) {
inline void foreachContourVertex(RawShape& sh, Fn fn) {
for(auto it = begin(sh); it != end(sh); ++it) fn(*it);
}
template<class RawShape, class Fn>
static inline void foreachHoleVertex(RawShape& sh, Fn fn) {
inline void foreachHoleVertex(RawShape& sh, Fn fn) {
for(int i = 0; i < holeCount(sh); ++i) {
auto& h = getHole(sh, i);
for(auto it = begin(h); it != end(h); ++it) fn(*it);
@ -766,12 +785,12 @@ struct ShapeLike {
}
template<class RawShape, class Fn>
static inline void foreachContourVertex(const RawShape& sh, Fn fn) {
inline void foreachContourVertex(const RawShape& sh, Fn fn) {
for(auto it = cbegin(sh); it != cend(sh); ++it) fn(*it);
}
template<class RawShape, class Fn>
static inline void foreachHoleVertex(const RawShape& sh, Fn fn) {
inline void foreachHoleVertex(const RawShape& sh, Fn fn) {
for(int i = 0; i < holeCount(sh); ++i) {
auto& h = getHole(sh, i);
for(auto it = cbegin(h); it != cend(h); ++it) fn(*it);
@ -779,18 +798,27 @@ struct ShapeLike {
}
template<class RawShape, class Fn>
static inline void foreachVertex(RawShape& sh, Fn fn) {
inline void foreachVertex(RawShape& sh, Fn fn) {
foreachContourVertex(sh, fn);
foreachHoleVertex(sh, fn);
}
template<class RawShape, class Fn>
static inline void foreachVertex(const RawShape& sh, Fn fn) {
inline void foreachVertex(const RawShape& sh, Fn fn) {
foreachContourVertex(sh, fn);
foreachHoleVertex(sh, fn);
}
}
};
#define DECLARE_MAIN_TYPES(T) \
using Polygon = T; \
using Point = TPoint<T>; \
using Coord = TCoord<Point>; \
using Contour = TContour<T>; \
using Box = _Box<Point>; \
using Circle = _Circle<Point>; \
using Segment = _Segment<Point>; \
using Polygons = TMultiShape<T>
}

View File

@ -9,6 +9,27 @@
namespace libnest2d {
namespace __nfp {
// Do not specialize this...
template<class RawShape>
inline bool _vsort(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2)
{
using Coord = TCoord<TPoint<RawShape>>;
Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2);
auto diff = y1 - y2;
if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
return x1 < x2;
return diff < 0;
}
}
/// A collection of static methods for handling the no fit polygon creation.
namespace nfp {
//namespace sl = shapelike;
//namespace pl = pointlike;
/// The complexity level of a polygon that an NFP implementation can handle.
enum class NfpLevel: unsigned {
CONVEX_ONLY,
@ -18,12 +39,17 @@ enum class NfpLevel: unsigned {
BOTH_CONCAVE_WITH_HOLES
};
/// A collection of static methods for handling the no fit polygon creation.
struct Nfp {
template<class RawShape>
using NfpResult = std::pair<RawShape, TPoint<RawShape>>;
template<class RawShape> struct MaxNfpLevel {
static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY;
};
// Shorthand for a pile of polygons
template<class RawShape>
using Shapes = typename ShapeLike::Shapes<RawShape>;
using Shapes = TMultiShape<RawShape>;
/**
* Merge a bunch of polygons with the specified additional polygon.
@ -36,10 +62,10 @@ using Shapes = typename ShapeLike::Shapes<RawShape>;
* mostly it will be a set containing only one big polygon but if the input
* polygons are disjuct than the resulting set will contain more polygons.
*/
template<class RawShape>
static Shapes<RawShape> merge(const Shapes<RawShape>& /*shc*/)
template<class RawShapes>
inline RawShapes merge(const RawShapes& /*shc*/)
{
static_assert(always_false<RawShape>::value,
static_assert(always_false<RawShapes>::value,
"Nfp::merge(shapes, shape) unimplemented!");
}
@ -55,12 +81,44 @@ static Shapes<RawShape> merge(const Shapes<RawShape>& /*shc*/)
* polygons are disjuct than the resulting set will contain more polygons.
*/
template<class RawShape>
static Shapes<RawShape> merge(const Shapes<RawShape>& shc,
const RawShape& sh)
inline TMultiShape<RawShape> merge(const TMultiShape<RawShape>& shc,
const RawShape& sh)
{
auto m = merge(shc);
auto m = nfp::merge(shc);
m.push_back(sh);
return merge(m);
return nfp::merge(m);
}
/**
* Get the vertex of the polygon that is at the lowest values (bottom) in the Y
* axis and if there are more than one vertices on the same Y coordinate than
* the result will be the leftmost (with the highest X coordinate).
*/
template<class RawShape>
inline TPoint<RawShape> leftmostDownVertex(const RawShape& sh)
{
// find min x and min y vertex
auto it = std::min_element(shapelike::cbegin(sh), shapelike::cend(sh),
__nfp::_vsort<RawShape>);
return it == shapelike::cend(sh) ? TPoint<RawShape>() : *it;;
}
/**
* Get the vertex of the polygon that is at the highest values (top) in the Y
* axis and if there are more than one vertices on the same Y coordinate than
* the result will be the rightmost (with the lowest X coordinate).
*/
template<class RawShape>
TPoint<RawShape> rightmostUpVertex(const RawShape& sh)
{
// find max x and max y vertex
auto it = std::max_element(shapelike::cbegin(sh), shapelike::cend(sh),
__nfp::_vsort<RawShape>);
return it == shapelike::cend(sh) ? TPoint<RawShape>() : *it;
}
/**
@ -71,55 +129,11 @@ static Shapes<RawShape> merge(const Shapes<RawShape>& shc,
* reference will be always the same for the same polygon.
*/
template<class RawShape>
inline static TPoint<RawShape> referenceVertex(const RawShape& sh)
inline TPoint<RawShape> referenceVertex(const RawShape& sh)
{
return rightmostUpVertex(sh);
}
/**
* Get the vertex of the polygon that is at the lowest values (bottom) in the Y
* axis and if there are more than one vertices on the same Y coordinate than
* the result will be the leftmost (with the highest X coordinate).
*/
template<class RawShape>
static TPoint<RawShape> leftmostDownVertex(const RawShape& sh)
{
// find min x and min y vertex
auto it = std::min_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh),
_vsort<RawShape>);
return *it;
}
/**
* Get the vertex of the polygon that is at the highest values (top) in the Y
* axis and if there are more than one vertices on the same Y coordinate than
* the result will be the rightmost (with the lowest X coordinate).
*/
template<class RawShape>
static TPoint<RawShape> rightmostUpVertex(const RawShape& sh)
{
// find max x and max y vertex
auto it = std::max_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh),
_vsort<RawShape>);
return *it;
}
template<class RawShape>
using NfpResult = std::pair<RawShape, TPoint<RawShape>>;
/// Helper function to get the NFP
template<NfpLevel nfptype, class RawShape>
static NfpResult<RawShape> noFitPolygon(const RawShape& sh,
const RawShape& other)
{
NfpImpl<RawShape, nfptype> nfp;
return nfp(sh, other);
}
/**
* The "trivial" Cuninghame-Green implementation of NFP for convex polygons.
*
@ -139,11 +153,11 @@ static NfpResult<RawShape> noFitPolygon(const RawShape& sh,
*
*/
template<class RawShape>
static NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
const RawShape& other)
{
using Vertex = TPoint<RawShape>; using Edge = _Segment<Vertex>;
using sl = ShapeLike;
namespace sl = shapelike;
RawShape rsh; // Final nfp placeholder
Vertex top_nfp;
@ -187,7 +201,7 @@ static NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
sl::addVertex(rsh, edgelist.front().second());
// Sorting function for the nfp reference vertex search
auto& cmp = _vsort<RawShape>;
auto& cmp = __nfp::_vsort<RawShape>;
// the reference (rightmost top) vertex so far
top_nfp = *std::max_element(sl::cbegin(rsh), sl::cend(rsh), cmp );
@ -214,7 +228,7 @@ static NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
}
template<class RawShape>
static NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
const RawShape& cother)
{
@ -233,7 +247,7 @@ static NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
using Vertex = TPoint<RawShape>;
using Coord = TCoord<Vertex>;
using Edge = _Segment<Vertex>;
using sl = ShapeLike;
namespace sl = shapelike;
using std::signbit;
using std::sort;
using std::vector;
@ -528,27 +542,16 @@ struct NfpImpl {
}
};
template<class RawShape> struct MaxNfpLevel {
static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY;
};
private:
// Do not specialize this...
template<class RawShape>
static inline bool _vsort(const TPoint<RawShape>& v1,
const TPoint<RawShape>& v2)
/// Helper function to get the NFP
template<NfpLevel nfptype, class RawShape>
inline NfpResult<RawShape> noFitPolygon(const RawShape& sh,
const RawShape& other)
{
using Coord = TCoord<TPoint<RawShape>>;
Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2);
auto diff = y1 - y2;
if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
return x1 < x2;
return diff < 0;
NfpImpl<RawShape, nfptype> nfps;
return nfps(sh, other);
}
};
}
}

View File

@ -9,10 +9,12 @@
#include <functional>
#include "geometry_traits.hpp"
#include "optimizer.hpp"
namespace libnest2d {
namespace sl = shapelike;
namespace pl = pointlike;
/**
* \brief An item to be placed on a bin.
*
@ -28,7 +30,8 @@ class _Item {
using Coord = TCoord<TPoint<RawShape>>;
using Vertex = TPoint<RawShape>;
using Box = _Box<Vertex>;
using sl = ShapeLike;
using VertexConstIterator = typename TContour<RawShape>::const_iterator;
// The original shape that gets encapsulated.
RawShape sh_;
@ -38,7 +41,7 @@ class _Item {
Radians rotation_;
Coord offset_distance_;
// Info about whether the tranformations will have to take place
// 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_offset_ = false;
@ -58,12 +61,12 @@ class _Item {
};
mutable Convexity convexity_ = Convexity::UNCHECKED;
mutable TVertexConstIterator<RawShape> rmt_; // rightmost top vertex
mutable TVertexConstIterator<RawShape> lmb_; // leftmost bottom vertex
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; Vertex tr;
BBCache(): valid(false), tr(0, 0) {}
Box bb; bool valid;
BBCache(): valid(false) {}
} bb_cache_;
public:
@ -80,7 +83,7 @@ public:
* supports. Giving out a non const iterator would make it impossible to
* perform correct cache invalidation.
*/
using Iterator = TVertexConstIterator<RawShape>;
using Iterator = VertexConstIterator;
/**
* @brief Get the orientation of the polygon.
@ -109,7 +112,7 @@ public:
explicit inline _Item(RawShape&& sh): sh_(std::move(sh)) {}
/**
* @brief Create an item from an initilizer list.
* @brief Create an item from an initializer list.
* @param il The initializer list of vertices.
*/
inline _Item(const std::initializer_list< Vertex >& il):
@ -159,7 +162,7 @@ public:
}
/**
* @brief Get a copy of an outer vertex whithin the carried shape.
* @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
@ -244,7 +247,7 @@ public:
* @param p
* @return
*/
inline bool isPointInside(const Vertex& p) const
inline bool isInside(const Vertex& p) const
{
return sl::isInside(p, transformedShape());
}
@ -307,7 +310,7 @@ public:
{
if(translation_ != tr) {
translation_ = tr; has_translation_ = true; tr_cache_valid_ = false;
bb_cache_.valid = false;
//bb_cache_.valid = false;
}
}
@ -342,13 +345,19 @@ public:
inline Box boundingBox() const {
if(!bb_cache_.valid) {
bb_cache_.bb = sl::boundingBox(transformedShape());
bb_cache_.tr = {0, 0};
if(!has_rotation_)
bb_cache_.bb = sl::boundingBox(offsettedShape());
else {
// TODO make sure this works
auto rotsh = offsettedShape();
sl::rotate(rotsh, rotation_);
bb_cache_.bb = sl::boundingBox(rotsh);
}
bb_cache_.valid = true;
}
auto &bb = bb_cache_.bb; auto &tr = bb_cache_.tr;
return {bb.minCorner() + tr, bb.maxCorner() + tr};
auto &bb = bb_cache_.bb; auto &tr = translation_;
return {bb.minCorner() + tr, bb.maxCorner() + tr };
}
inline Vertex referenceVertex() const {
@ -438,7 +447,7 @@ public:
inline _Rectangle(Unit width, Unit height,
// disable this ctor if o != CLOCKWISE
enable_if_t< o == TO::CLOCKWISE, int> = 0 ):
_Item<RawShape>( ShapeLike::create<RawShape>( {
_Item<RawShape>( sl::create<RawShape>( {
{0, 0},
{0, height},
{width, height},
@ -452,7 +461,7 @@ public:
inline _Rectangle(Unit width, Unit height,
// disable this ctor if o != COUNTER_CLOCKWISE
enable_if_t< o == TO::COUNTER_CLOCKWISE, int> = 0 ):
_Item<RawShape>( ShapeLike::create<RawShape>( {
_Item<RawShape>( sl::create<RawShape>( {
{0, 0},
{width, 0},
{width, height},
@ -473,18 +482,38 @@ public:
template<class RawShape>
inline bool _Item<RawShape>::isInside(const _Box<TPoint<RawShape>>& box) const {
return ShapeLike::isInside<RawShape>(boundingBox(), box);
return sl::isInside<RawShape>(boundingBox(), box);
}
template<class RawShape> inline bool
_Item<RawShape>::isInside(const _Circle<TPoint<RawShape>>& circ) const {
return ShapeLike::isInside<RawShape>(transformedShape(), circ);
return sl::isInside<RawShape>(transformedShape(), circ);
}
template<class I> using _ItemRef = std::reference_wrapper<I>;
template<class I> using _ItemGroup = std::vector<_ItemRef<I>>;
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 want's to use its own placement algorithm, all it has to do is to
* 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
@ -515,8 +544,9 @@ public:
*/
using PackResult = typename PlacementStrategy::PackResult;
using ItemRef = std::reference_wrapper<Item>;
using ItemGroup = std::vector<ItemRef>;
using ItemRef = _ItemRef<Item>;
using ItemGroup = _ItemGroup<Item>;
using DefaultIterator = typename ItemGroup::const_iterator;
/**
* @brief Constructor taking the bin and an optional configuration.
@ -536,29 +566,32 @@ public:
* 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 startegy.
* @param config The configuration object defined by the placement strategy.
*/
inline void configure(const Config& config) { impl_.configure(config); }
/**
* @brief A method that tries to pack an item and returns an object
* describing the pack result.
* Try to pack an item with a result object that contains the packing
* information for later accepting it.
*
* The result can be casted to bool and used as an argument to the accept
* method to accept a succesfully packed item. This way the next packing
* will consider the accepted item as well. The PackResult should carry the
* transformation info so that if the tried item is later modified or tried
* multiple times, the result object should set it to the originally
* determied position. An implementation can be found in the
* strategies::PlacerBoilerplate::PackResult class.
*
* @param item Ithe item to be packed.
* @return The PackResult object that can be implicitly casted to bool.
* \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.
*/
inline PackResult trypack(Item& item) { return impl_.trypack(item); }
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.
* @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.
@ -566,19 +599,25 @@ public:
inline void accept(PackResult& r) { impl_.accept(r); }
/**
* @brief pack Try to pack an item and immediately accept it on success.
* @brief pack Try to pack and immediately accept it on success.
*
* A default implementation would be to call
* { auto&& r = trypack(item); accept(r); return r; } but we should let the
* { 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 intermadiate step. The above version can still be used
* 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.
*/
inline bool pack(Item& item) { return impl_.pack(item); }
template<class Range = ConstItemRange<DefaultIterator>>
inline bool pack(
Item& item,
const Range& remaining = Range())
{
return impl_.pack(item, remaining);
}
/// Unpack the last element (remove it from the list of packed items).
inline void unpackLast() { impl_.unpackLast(); }
@ -597,13 +636,6 @@ public:
inline double filledArea() const { return impl_.filledArea(); }
#ifndef NDEBUG
inline auto getDebugItems() -> decltype(impl_.debug_items_)&
{
return impl_.debug_items_;
}
#endif
};
// The progress function will be called with the number of placed items
@ -628,15 +660,15 @@ public:
* 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 startegy.
* @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 whenewer an item or
* a group of items where succesfully packed.
* @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.
*/
@ -649,7 +681,7 @@ public:
* 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 converitible to Item.
* 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.
@ -681,7 +713,7 @@ public:
/**
* @brief Get the items for a particular bin.
* @param binIndex The index of the requested bin.
* @return Returns a list of allitems packed into the requested bin.
* @return Returns a list of all items packed into the requested bin.
*/
inline ItemGroup itemsForBin(size_t binIndex) {
return impl_.itemsForBin(binIndex);
@ -723,15 +755,14 @@ using _IndexedPackGroup = std::vector<
>;
/**
* The Arranger is the frontend class for the binpack2d library. It takes the
* The Arranger is the front-end class for the libnest2d library. It takes the
* input items and outputs the items with the proper transformations to be
* inside the provided bin.
*/
template<class PlacementStrategy, class SelectionStrategy >
class Arranger {
class Nester {
using TSel = SelectionStrategyLike<SelectionStrategy>;
TSel selector_;
bool use_min_bb_rotation_ = false;
public:
using Item = typename PlacementStrategy::Item;
using ItemRef = std::reference_wrapper<Item>;
@ -769,7 +800,7 @@ public:
template<class TBinType = BinType,
class PConf = PlacementConfig,
class SConf = SelectionConfig>
Arranger( TBinType&& bin,
Nester( TBinType&& bin,
Unit min_obj_distance = 0,
PConf&& pconfig = PConf(),
SConf&& sconfig = SConf()):
@ -802,9 +833,9 @@ public:
* the selection algorithm.
*/
template<class TIterator>
inline PackGroup arrange(TIterator from, TIterator to)
inline PackGroup execute(TIterator from, TIterator to)
{
return _arrange(from, to);
return _execute(from, to);
}
/**
@ -815,20 +846,20 @@ public:
* input sequence size.
*/
template<class TIterator>
inline IndexedPackGroup arrangeIndexed(TIterator from, TIterator to)
inline IndexedPackGroup executeIndexed(TIterator from, TIterator to)
{
return _arrangeIndexed(from, to);
return _executeIndexed(from, to);
}
/// Shorthand to normal arrange method.
template<class TIterator>
inline PackGroup operator() (TIterator from, TIterator to)
{
return _arrange(from, to);
return _execute(from, to);
}
/// Set a progress indicatior function object for the selector.
inline Arranger& progressIndicator(ProgressFunction func)
/// Set a progress indicator function object for the selector.
inline Nester& progressIndicator(ProgressFunction func)
{
selector_.progressIndicator(func); return *this;
}
@ -842,24 +873,20 @@ public:
return ret;
}
inline Arranger& useMinimumBoundigBoxRotation(bool s = true) {
use_min_bb_rotation_ = s; return *this;
}
private:
template<class TIterator,
class IT = remove_cvref_t<typename TIterator::value_type>,
// This funtion will be used only if the iterators are pointing to
// a type compatible with the binpack2d::_Item template.
// This function will be used only if the iterators are pointing to
// a type compatible with the libnets2d::_Item template.
// This way we can use references to input elements as they will
// have to exist for the lifetime of this call.
class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT>
>
inline PackGroup _arrange(TIterator from, TIterator to, bool = false)
inline PackGroup _execute(TIterator from, TIterator to, bool = false)
{
__arrange(from, to);
__execute(from, to);
return lastResult();
}
@ -867,28 +894,28 @@ private:
class IT = remove_cvref_t<typename TIterator::value_type>,
class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT>
>
inline PackGroup _arrange(TIterator from, TIterator to, int = false)
inline PackGroup _execute(TIterator from, TIterator to, int = false)
{
item_cache_ = {from, to};
__arrange(item_cache_.begin(), item_cache_.end());
__execute(item_cache_.begin(), item_cache_.end());
return lastResult();
}
template<class TIterator,
class IT = remove_cvref_t<typename TIterator::value_type>,
// This funtion will be used only if the iterators are pointing to
// a type compatible with the binpack2d::_Item template.
// This function will be used only if the iterators are pointing to
// a type compatible with the libnest2d::_Item template.
// This way we can use references to input elements as they will
// have to exist for the lifetime of this call.
class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT>
>
inline IndexedPackGroup _arrangeIndexed(TIterator from,
inline IndexedPackGroup _executeIndexed(TIterator from,
TIterator to,
bool = false)
{
__arrange(from, to);
__execute(from, to);
return createIndexedPackGroup(from, to, selector_);
}
@ -896,12 +923,12 @@ private:
class IT = remove_cvref_t<typename TIterator::value_type>,
class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT>
>
inline IndexedPackGroup _arrangeIndexed(TIterator from,
inline IndexedPackGroup _executeIndexed(TIterator from,
TIterator to,
int = false)
{
item_cache_ = {from, to};
__arrange(item_cache_.begin(), item_cache_.end());
__execute(item_cache_.begin(), item_cache_.end());
return createIndexedPackGroup(from, to, selector_);
}
@ -933,37 +960,12 @@ private:
return pg;
}
Radians findBestRotation(Item& item) {
opt::StopCriteria stopcr;
stopcr.absolute_score_difference = 0.01;
stopcr.max_iterations = 10000;
opt::TOptimizer<opt::Method::G_GENETIC> solver(stopcr);
auto orig_rot = item.rotation();
auto result = solver.optimize_min([&item, &orig_rot](Radians rot){
item.rotation(orig_rot + rot);
auto bb = item.boundingBox();
return std::sqrt(bb.height()*bb.width());
}, opt::initvals(Radians(0)), opt::bound<Radians>(-Pi/2, Pi/2));
item.rotation(orig_rot);
return std::get<0>(result.optimum);
}
template<class TIter> inline void __arrange(TIter from, TIter to)
template<class TIter> inline void __execute(TIter from, TIter to)
{
if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) {
item.addOffset(static_cast<Unit>(std::ceil(min_obj_distance_/2.0)));
});
if(use_min_bb_rotation_)
std::for_each(from, to, [this](Item& item){
Radians rot = findBestRotation(item);
item.rotate(rot);
});
selector_.template packItems<PlacementStrategy>(
from, to, bin_, pconfig_);

View File

@ -67,11 +67,11 @@ class metaloop {
// need to wrap that in a type (see metaloop::Int).
/*
* A helper alias to create integer values wrapped as a type. It is nessecary
* A helper alias to create integer values wrapped as a type. It is necessary
* because a non type template parameter (such as int) would be prohibited in
* a partial specialization. Also for the same reason we have to use a class
* _Metaloop instead of a simple function as a functor. A function cannot be
* partially specialized in a way that is neccesary for this trick.
* partially specialized in a way that is necessary for this trick.
*/
template<int N> using Int = std::integral_constant<int, N>;
@ -88,7 +88,7 @@ public:
// It takes the real functor that can be specified in-place but only
// with C++14 because the second parameter's type will depend on the
// type of the parameter pack element that is processed. In C++14 we can
// specify this second parameter type as auto in the lamda parameter list.
// specify this second parameter type as auto in the lambda parameter list.
inline MapFn(Fn&& fn): fn_(forward<Fn>(fn)) {}
template<class T> void operator ()(T&& pack_element) {
@ -146,7 +146,7 @@ public:
* version of run is called which does not call itself anymore.
*
* If you are utterly annoyed, at least you have learned a super crazy
* functional metaprogramming pattern.
* functional meta-programming pattern.
*/
template<class...Args>
using MetaLoop = _MetaLoop<Int<sizeof...(Args)-1>, Args...>;

View File

@ -102,6 +102,9 @@ struct StopCriteria {
/// If the relative value difference between two scores.
double relative_score_difference = std::nan("");
/// Stop if this value or better is found.
double stop_score = std::nan("");
unsigned max_iterations = 0;
};

View File

@ -142,10 +142,12 @@ protected:
default: ;
}
auto abs_diff = stopcr_.absolute_score_difference;
auto rel_diff = stopcr_.relative_score_difference;
double abs_diff = stopcr_.absolute_score_difference;
double rel_diff = stopcr_.relative_score_difference;
double stopval = stopcr_.stop_score;
if(!std::isnan(abs_diff)) opt_.set_ftol_abs(abs_diff);
if(!std::isnan(rel_diff)) opt_.set_ftol_rel(rel_diff);
if(!std::isnan(stopval)) opt_.set_stopval(stopval);
if(this->stopcr_.max_iterations > 0)
opt_.set_maxeval(this->stopcr_.max_iterations );

View File

@ -5,11 +5,26 @@
#include "placer_boilerplate.hpp"
namespace libnest2d { namespace strategies {
namespace libnest2d { namespace placers {
template<class T, class = T> struct Epsilon {};
template<class T>
struct Epsilon<T, enable_if_t<std::is_integral<T>::value, T> > {
static const T Value = 1;
};
template<class T>
struct Epsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > {
static const T Value = 1e-3;
};
template<class RawShape>
struct BLConfig {
TCoord<TPoint<RawShape>> min_obj_distance = 0;
DECLARE_MAIN_TYPES(RawShape);
Coord min_obj_distance = 0;
Coord epsilon = Epsilon<Coord>::Value;
bool allow_rotations = false;
};
@ -27,9 +42,13 @@ public:
explicit _BottomLeftPlacer(const BinType& bin): Base(bin) {}
PackResult trypack(Item& item) {
template<class Range = ConstItemRange<typename Base::DefaultIter>>
PackResult trypack(Item& item,
const Range& = Range())
{
auto r = _trypack(item);
if(!r && Base::config_.allow_rotations) {
item.rotate(Degrees(90));
r =_trypack(item);
}
@ -65,20 +84,21 @@ protected:
setInitialPosition(item);
Unit d = availableSpaceDown(item);
bool can_move = d > 1 /*std::numeric_limits<Unit>::epsilon()*/;
auto eps = config_.epsilon;
bool can_move = d > eps;
bool can_be_packed = can_move;
bool left = true;
while(can_move) {
if(left) { // write previous down move and go down
item.translate({0, -d+1});
item.translate({0, -d+eps});
d = availableSpaceLeft(item);
can_move = d > 1/*std::numeric_limits<Unit>::epsilon()*/;
can_move = d > eps;
left = false;
} else { // write previous left move and go down
item.translate({-d+1, 0});
item.translate({-d+eps, 0});
d = availableSpaceDown(item);
can_move = d > 1/*std::numeric_limits<Unit>::epsilon()*/;
can_move = d > eps;
left = true;
}
}
@ -112,10 +132,10 @@ protected:
const RawShape& scanpoly)
{
auto tsh = other.transformedShape();
return ( ShapeLike::intersects(tsh, scanpoly) ||
ShapeLike::isInside(tsh, scanpoly) ) &&
( !ShapeLike::intersects(tsh, item.rawShape()) &&
!ShapeLike::isInside(tsh, item.rawShape()) );
return ( sl::intersects(tsh, scanpoly) ||
sl::isInside(tsh, scanpoly) ) &&
( !sl::intersects(tsh, item.rawShape()) &&
!sl::isInside(tsh, item.rawShape()) );
}
template<class C = Coord>
@ -126,25 +146,25 @@ protected:
{
auto tsh = other.transformedShape();
bool inters_scanpoly = ShapeLike::intersects(tsh, scanpoly) &&
!ShapeLike::touches(tsh, scanpoly);
bool inters_item = ShapeLike::intersects(tsh, item.rawShape()) &&
!ShapeLike::touches(tsh, item.rawShape());
bool inters_scanpoly = sl::intersects(tsh, scanpoly) &&
!sl::touches(tsh, scanpoly);
bool inters_item = sl::intersects(tsh, item.rawShape()) &&
!sl::touches(tsh, item.rawShape());
return ( inters_scanpoly ||
ShapeLike::isInside(tsh, scanpoly)) &&
sl::isInside(tsh, scanpoly)) &&
( !inters_item &&
!ShapeLike::isInside(tsh, item.rawShape())
!sl::isInside(tsh, item.rawShape())
);
}
Container itemsInTheWayOf(const Item& item, const Dir dir) {
ItemGroup itemsInTheWayOf(const Item& item, const Dir dir) {
// Get the left or down polygon, that has the same area as the shadow
// of input item reflected to the left or downwards
auto&& scanpoly = dir == Dir::LEFT? leftPoly(item) :
downPoly(item);
Container ret; // packed items 'in the way' of item
ItemGroup ret; // packed items 'in the way' of item
ret.reserve(items_.size());
// Predicate to find items that are 'in the way' for left (down) move
@ -173,18 +193,18 @@ protected:
if(dir == Dir::LEFT) {
getCoord = [](const Vertex& v) { return getX(v); };
availableDistance = PointLike::horizontalDistance<Vertex>;
availableDistance = pointlike::horizontalDistance<Vertex>;
availableDistanceSV = [](const Segment& s, const Vertex& v) {
auto ret = PointLike::horizontalDistance<Vertex>(v, s);
auto ret = pointlike::horizontalDistance<Vertex>(v, s);
if(ret.second) ret.first = -ret.first;
return ret;
};
}
else {
getCoord = [](const Vertex& v) { return getY(v); };
availableDistance = PointLike::verticalDistance<Vertex>;
availableDistance = pointlike::verticalDistance<Vertex>;
availableDistanceSV = [](const Segment& s, const Vertex& v) {
auto ret = PointLike::verticalDistance<Vertex>(v, s);
auto ret = pointlike::verticalDistance<Vertex>(v, s);
if(ret.second) ret.first = -ret.first;
return ret;
};
@ -214,9 +234,9 @@ protected:
assert(pleft.vertexCount() > 0);
auto trpleft = pleft.transformedShape();
auto first = ShapeLike::begin(trpleft);
auto first = sl::begin(trpleft);
auto next = first + 1;
auto endit = ShapeLike::end(trpleft);
auto endit = sl::end(trpleft);
while(next != endit) {
Segment seg(*(first++), *(next++));
@ -340,16 +360,16 @@ protected:
// reserve for all vertices plus 2 for the left horizontal wall, 2 for
// the additional vertices for maintaning min object distance
ShapeLike::reserve(rsh, finish-start+4);
sl::reserve(rsh, finish-start+4);
/*auto addOthers = [&rsh, finish, start, &item](){
for(size_t i = start+1; i < finish; i++)
ShapeLike::addVertex(rsh, item.vertex(i));
sl::addVertex(rsh, item.vertex(i));
};*/
auto reverseAddOthers = [&rsh, finish, start, &item](){
for(auto i = finish-1; i > start; i--)
ShapeLike::addVertex(rsh, item.vertex(
sl::addVertex(rsh, item.vertex(
static_cast<unsigned long>(i)));
};
@ -361,25 +381,25 @@ protected:
// Clockwise polygon construction
ShapeLike::addVertex(rsh, topleft_vertex);
sl::addVertex(rsh, topleft_vertex);
if(dir == Dir::LEFT) reverseAddOthers();
else {
ShapeLike::addVertex(rsh, getX(topleft_vertex), 0);
ShapeLike::addVertex(rsh, getX(bottomleft_vertex), 0);
sl::addVertex(rsh, getX(topleft_vertex), 0);
sl::addVertex(rsh, getX(bottomleft_vertex), 0);
}
ShapeLike::addVertex(rsh, bottomleft_vertex);
sl::addVertex(rsh, bottomleft_vertex);
if(dir == Dir::LEFT) {
ShapeLike::addVertex(rsh, 0, getY(bottomleft_vertex));
ShapeLike::addVertex(rsh, 0, getY(topleft_vertex));
sl::addVertex(rsh, 0, getY(bottomleft_vertex));
sl::addVertex(rsh, 0, getY(topleft_vertex));
}
else reverseAddOthers();
// Close the polygon
ShapeLike::addVertex(rsh, topleft_vertex);
sl::addVertex(rsh, topleft_vertex);
return rsh;
}

File diff suppressed because it is too large Load Diff

View File

@ -3,14 +3,11 @@
#include "../libnest2d.hpp"
namespace libnest2d { namespace strategies {
namespace libnest2d { namespace placers {
struct EmptyConfig {};
template<class Subclass, class RawShape, class TBin,
class Cfg = EmptyConfig,
class Store = std::vector<std::reference_wrapper<_Item<RawShape>>>
>
template<class Subclass, class RawShape, class TBin, class Cfg = EmptyConfig>
class PlacerBoilerplate {
mutable bool farea_valid_ = false;
mutable double farea_ = 0.0;
@ -22,25 +19,30 @@ public:
using Coord = TCoord<Vertex>;
using Unit = Coord;
using Config = Cfg;
using Container = Store;
using ItemGroup = _ItemGroup<Item>;
using DefaultIter = typename ItemGroup::const_iterator;
class PackResult {
Item *item_ptr_;
Vertex move_;
Radians rot_;
double overfit_;
friend class PlacerBoilerplate;
friend Subclass;
PackResult(Item& item):
item_ptr_(&item),
move_(item.translation()),
rot_(item.rotation()) {}
PackResult(): item_ptr_(nullptr) {}
PackResult(double overfit = 1.0):
item_ptr_(nullptr), overfit_(overfit) {}
public:
operator bool() { return item_ptr_ != nullptr; }
double overfit() const { return overfit_; }
};
using ItemGroup = const Container&;
inline PlacerBoilerplate(const BinType& bin, unsigned cap = 50): bin_(bin)
{
items_.reserve(cap);
@ -56,8 +58,10 @@ public:
config_ = config;
}
bool pack(Item& item) {
auto&& r = static_cast<Subclass*>(this)->trypack(item);
template<class Range = ConstItemRange<DefaultIter>>
bool pack(Item& item,
const Range& rem = Range()) {
auto&& r = static_cast<Subclass*>(this)->trypack(item, rem);
if(r) {
items_.push_back(*(r.item_ptr_));
farea_valid_ = false;
@ -79,14 +83,11 @@ public:
farea_valid_ = false;
}
inline ItemGroup getItems() const { return items_; }
inline const ItemGroup& getItems() const { return items_; }
inline void clearItems() {
items_.clear();
farea_valid_ = false;
#ifndef NDEBUG
debug_items_.clear();
#endif
}
inline double filledArea() const {
@ -103,14 +104,10 @@ public:
return farea_;
}
#ifndef NDEBUG
std::vector<Item> debug_items_;
#endif
protected:
BinType bin_;
Container items_;
ItemGroup items_;
Cfg config_;
};
@ -121,6 +118,7 @@ using Base::items_; \
using Base::config_; \
public: \
using typename Base::Item; \
using typename Base::ItemGroup; \
using typename Base::BinType; \
using typename Base::Config; \
using typename Base::Vertex; \
@ -128,7 +126,6 @@ using typename Base::Segment; \
using typename Base::PackResult; \
using typename Base::Coord; \
using typename Base::Unit; \
using typename Base::Container; \
private:
}

View File

@ -0,0 +1,41 @@
#ifndef ROTFINDER_HPP
#define ROTFINDER_HPP
#include <libnest2d/libnest2d.hpp>
#include <libnest2d/optimizer.hpp>
#include <iterator>
namespace libnest2d {
template<class RawShape>
Radians findBestRotation(_Item<RawShape>& item) {
opt::StopCriteria stopcr;
stopcr.absolute_score_difference = 0.01;
stopcr.max_iterations = 10000;
opt::TOptimizer<opt::Method::G_GENETIC> solver(stopcr);
auto orig_rot = item.rotation();
auto result = solver.optimize_min([&item, &orig_rot](Radians rot){
item.rotation(orig_rot + rot);
auto bb = item.boundingBox();
return std::sqrt(bb.height()*bb.width());
}, opt::initvals(Radians(0)), opt::bound<Radians>(-Pi/2, Pi/2));
item.rotation(orig_rot);
return std::get<0>(result.optimum);
}
template<class Iterator>
void findMinimumBoundingBoxRotations(Iterator from, Iterator to) {
using V = typename std::iterator_traits<Iterator>::value_type;
std::for_each(from, to, [](V& item){
Radians rot = findBestRotation(item);
item.rotate(rot);
});
}
}
#endif // ROTFINDER_HPP

View File

@ -8,7 +8,7 @@
#include "selection_boilerplate.hpp"
namespace libnest2d { namespace strategies {
namespace libnest2d { namespace selections {
/**
* Selection heuristic based on [López-Camacho]\
@ -118,7 +118,7 @@ public:
using Placer = PlacementStrategyLike<TPlacer>;
using ItemList = std::list<ItemRef>;
const double bin_area = ShapeLike::area<RawShape>(bin);
const double bin_area = sl::area(bin);
const double w = bin_area * config_.waste_increment;
const double INITIAL_FILL_PROPORTION = config_.initial_fill_proportion;
@ -227,10 +227,14 @@ public:
bool ret = false;
auto it = not_packed.begin();
auto pack = [&placer, &not_packed](ItemListIt it) {
return placer.pack(*it, rem(it, not_packed));
};
while(it != not_packed.end() && !ret &&
free_area - (item_area = it->get().area()) <= waste)
{
if(item_area <= free_area && placer.pack(*it) ) {
if(item_area <= free_area && pack(it) ) {
free_area -= item_area;
filled_area = bin_area - free_area;
ret = true;
@ -270,6 +274,11 @@ public:
auto it2 = it;
std::vector<TPair> wrong_pairs;
using std::placeholders::_1;
auto trypack = [&placer, &not_packed](ItemListIt it) {
return placer.trypack(*it, rem(it, not_packed));
};
while(it != endit && !ret &&
free_area - (item_area = it->get().area()) -
@ -278,7 +287,7 @@ public:
if(item_area + smallestPiece(it, not_packed)->get().area() >
free_area ) { it++; continue; }
auto pr = placer.trypack(*it);
auto pr = trypack(it);
// First would fit
it2 = not_packed.begin();
@ -294,14 +303,14 @@ public:
}
placer.accept(pr);
auto pr2 = placer.trypack(*it2);
auto pr2 = trypack(it2);
if(!pr2) {
placer.unpackLast(); // remove first
if(try_reverse) {
pr2 = placer.trypack(*it2);
pr2 = trypack(it2);
if(pr2) {
placer.accept(pr2);
auto pr12 = placer.trypack(*it);
auto pr12 = trypack(it);
if(pr12) {
placer.accept(pr12);
ret = true;
@ -365,6 +374,14 @@ public:
return it->get().area();
};
auto trypack = [&placer, &not_packed](ItemListIt it) {
return placer.trypack(*it, rem(it, not_packed));
};
auto pack = [&placer, &not_packed](ItemListIt it) {
return placer.pack(*it, rem(it, not_packed));
};
while (it != endit && !ret) { // drill down 1st level
// We need to determine in each iteration the largest, second
@ -394,7 +411,7 @@ public:
it++; continue;
}
auto pr = placer.trypack(*it);
auto pr = trypack(it);
// Check for free area and try to pack the 1st item...
if(!pr) { it++; continue; }
@ -420,15 +437,15 @@ public:
bool can_pack2 = false;
placer.accept(pr);
auto pr2 = placer.trypack(*it2);
auto pr2 = trypack(it2);
auto pr12 = pr;
if(!pr2) {
placer.unpackLast(); // remove first
if(try_reverse) {
pr2 = placer.trypack(*it2);
pr2 = trypack(it2);
if(pr2) {
placer.accept(pr2);
pr12 = placer.trypack(*it);
pr12 = trypack(it);
if(pr12) can_pack2 = true;
placer.unpackLast();
}
@ -463,7 +480,7 @@ public:
if(a3_sum > free_area) { it3++; continue; }
placer.accept(pr12); placer.accept(pr2);
bool can_pack3 = placer.pack(*it3);
bool can_pack3 = pack(it3);
if(!can_pack3) {
placer.unpackLast();
@ -473,16 +490,16 @@ public:
if(!can_pack3 && try_reverse) {
std::array<size_t, 3> indices = {0, 1, 2};
std::array<ItemRef, 3>
candidates = {*it, *it2, *it3};
std::array<typename ItemList::iterator, 3>
candidates = {it, it2, it3};
auto tryPack = [&placer, &candidates](
auto tryPack = [&placer, &candidates, &pack](
const decltype(indices)& idx)
{
std::array<bool, 3> packed = {false};
for(auto id : idx) packed.at(id) =
placer.pack(candidates[id]);
pack(candidates[id]);
bool check =
std::all_of(packed.begin(),
@ -536,7 +553,7 @@ public:
{ auto it = store_.begin();
while (it != store_.end()) {
Placer p(bin); p.configure(pconfig);
if(!p.pack(*it)) {
if(!p.pack(*it, rem(it, store_))) {
it = store_.erase(it);
} else it++;
}
@ -551,11 +568,7 @@ public:
{
packed_bins_[idx] = placer.getItems();
#ifndef NDEBUG
packed_bins_[idx].insert(packed_bins_[idx].end(),
placer.getDebugItems().begin(),
placer.getDebugItems().end());
#endif
// TODO here should be a spinlock
slock.lock();
acounter -= packednum;
@ -601,7 +614,7 @@ public:
while(it != not_packed.end() &&
filled_area < INITIAL_FILL_AREA)
{
if(placer.pack(*it)) {
if(placer.pack(*it, rem(it, not_packed))) {
filled_area += it->get().area();
free_area = bin_area - filled_area;
it = not_packed.erase(it);

View File

@ -3,7 +3,7 @@
#include "selection_boilerplate.hpp"
namespace libnest2d { namespace strategies {
namespace libnest2d { namespace selections {
template<class RawShape>
class _FillerSelection: public SelectionBoilerplate<RawShape> {
@ -56,18 +56,13 @@ public:
std::sort(store_.begin(), store_.end(), sortfunc);
// Container a = {store_[0], store_[1], store_[4], store_[5] };
//// a.insert(a.end(), store_.end()-10, store_.end());
// store_ = a;
PlacementStrategyLike<TPlacer> placer(bin);
placer.configure(pconfig);
auto it = store_.begin();
while(it != store_.end()) {
if(!placer.pack(*it)) {
if(!placer.pack(*it, {std::next(it), store_.end()})) {
if(packed_bins_.back().empty()) ++it;
// makeProgress(placer);
placer.clearItems();
packed_bins_.emplace_back();
} else {
@ -76,9 +71,6 @@ public:
}
}
// if(was_packed) {
// packed_bins_.push_back(placer.getItems());
// }
}
};

View File

@ -4,7 +4,7 @@
#include "../libnest2d.hpp"
#include "selection_boilerplate.hpp"
namespace libnest2d { namespace strategies {
namespace libnest2d { namespace selections {
template<class RawShape>
class _FirstFitSelection: public SelectionBoilerplate<RawShape> {
@ -40,6 +40,7 @@ public:
packed_bins_.clear();
std::vector<Placer> placers;
placers.reserve(last-first);
std::copy(first, last, std::back_inserter(store_));
@ -66,21 +67,25 @@ public:
}
}
for(auto& item : store_ ) {
bool was_packed = false;
while(!was_packed) {
auto it = store_.begin();
for(size_t j = 0; j < placers.size() && !was_packed; j++) {
if((was_packed = placers[j].pack(item)))
makeProgress(placers[j], j);
while(it != store_.end()) {
bool was_packed = false;
size_t j = 0;
while(!was_packed) {
for(; j < placers.size() && !was_packed; j++) {
if((was_packed = placers[j].pack(*it, rem(it, store_) )))
makeProgress(placers[j], j);
}
if(!was_packed) {
placers.emplace_back(bin);
placers.back().configure(pconfig);
packed_bins_.emplace_back();
j = placers.size() - 1;
}
}
++it;
}
}

View File

@ -3,8 +3,7 @@
#include "../libnest2d.hpp"
namespace libnest2d {
namespace strategies {
namespace libnest2d { namespace selections {
template<class RawShape>
class SelectionBoilerplate {

View File

@ -5,6 +5,7 @@
#include "printer_parts.h"
#include <libnest2d/geometry_traits_nfp.hpp>
//#include "../tools/libnfpglue.hpp"
//#include "../tools/nfp_svgnest_glue.hpp"
std::vector<libnest2d::Item>& prusaParts() {
static std::vector<libnest2d::Item> ret;
@ -99,6 +100,43 @@ TEST(BasicFunctionality, creationAndDestruction)
}
TEST(GeometryAlgorithms, boundingCircle) {
using namespace libnest2d;
using placers::boundingCircle;
PolygonImpl p = {{{0, 10}, {10, 0}, {0, -10}, {0, 10}}, {}};
Circle c = boundingCircle(p);
ASSERT_EQ(c.center().X, 0);
ASSERT_EQ(c.center().Y, 0);
ASSERT_DOUBLE_EQ(c.radius(), 10);
shapelike::translate(p, PointImpl{10, 10});
c = boundingCircle(p);
ASSERT_EQ(c.center().X, 10);
ASSERT_EQ(c.center().Y, 10);
ASSERT_DOUBLE_EQ(c.radius(), 10);
auto parts = prusaParts();
int i = 0;
for(auto& part : parts) {
c = boundingCircle(part.transformedShape());
if(std::isnan(c.radius())) std::cout << "fail: radius is nan" << std::endl;
else for(auto v : shapelike::getContour(part.transformedShape()) ) {
auto d = pointlike::distance(v, c.center());
if(d > c.radius() ) {
auto e = std::abs( 1.0 - d/c.radius());
ASSERT_LE(e, 1e-3);
}
}
i++;
}
}
TEST(GeometryAlgorithms, Distance) {
using namespace libnest2d;
@ -107,14 +145,14 @@ TEST(GeometryAlgorithms, Distance) {
Point p2 = {10, 0};
Point p3 = {10, 10};
ASSERT_DOUBLE_EQ(PointLike::distance(p1, p2), 10);
ASSERT_DOUBLE_EQ(PointLike::distance(p1, p3), sqrt(200));
ASSERT_DOUBLE_EQ(pointlike::distance(p1, p2), 10);
ASSERT_DOUBLE_EQ(pointlike::distance(p1, p3), sqrt(200));
Segment seg(p1, p3);
ASSERT_DOUBLE_EQ(PointLike::distance(p2, seg), 7.0710678118654755);
ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755);
auto result = PointLike::horizontalDistance(p2, seg);
auto result = pointlike::horizontalDistance(p2, seg);
auto check = [](Coord val, Coord expected) {
if(std::is_floating_point<Coord>::value)
@ -127,11 +165,11 @@ TEST(GeometryAlgorithms, Distance) {
ASSERT_TRUE(result.second);
check(result.first, 10);
result = PointLike::verticalDistance(p2, seg);
result = pointlike::verticalDistance(p2, seg);
ASSERT_TRUE(result.second);
check(result.first, -10);
result = PointLike::verticalDistance(Point{10, 20}, seg);
result = pointlike::verticalDistance(Point{10, 20}, seg);
ASSERT_TRUE(result.second);
check(result.first, 10);
@ -139,12 +177,12 @@ TEST(GeometryAlgorithms, Distance) {
Point p4 = {80, 0};
Segment seg2 = { {0, 0}, {0, 40} };
result = PointLike::horizontalDistance(p4, seg2);
result = pointlike::horizontalDistance(p4, seg2);
ASSERT_TRUE(result.second);
check(result.first, 80);
result = PointLike::verticalDistance(p4, seg2);
result = pointlike::verticalDistance(p4, seg2);
// Point should not be related to the segment
ASSERT_FALSE(result.second);
@ -172,7 +210,7 @@ TEST(GeometryAlgorithms, Area) {
{61, 97}
};
ASSERT_TRUE(ShapeLike::area(item.transformedShape()) > 0 );
ASSERT_TRUE(shapelike::area(item.transformedShape()) > 0 );
}
TEST(GeometryAlgorithms, IsPointInsidePolygon) {
@ -182,21 +220,21 @@ TEST(GeometryAlgorithms, IsPointInsidePolygon) {
Point p = {1, 1};
ASSERT_TRUE(rect.isPointInside(p));
ASSERT_TRUE(rect.isInside(p));
p = {11, 11};
ASSERT_FALSE(rect.isPointInside(p));
ASSERT_FALSE(rect.isInside(p));
p = {11, 12};
ASSERT_FALSE(rect.isPointInside(p));
ASSERT_FALSE(rect.isInside(p));
p = {3, 3};
ASSERT_TRUE(rect.isPointInside(p));
ASSERT_TRUE(rect.isInside(p));
}
@ -250,7 +288,7 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon)
Item leftp(placer.leftPoly(item));
ASSERT_TRUE(ShapeLike::isValid(leftp.rawShape()).first);
ASSERT_TRUE(shapelike::isValid(leftp.rawShape()).first);
ASSERT_EQ(leftp.vertexCount(), leftControl.vertexCount());
for(unsigned long i = 0; i < leftControl.vertexCount(); i++) {
@ -260,7 +298,7 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon)
Item downp(placer.downPoly(item));
ASSERT_TRUE(ShapeLike::isValid(downp.rawShape()).first);
ASSERT_TRUE(shapelike::isValid(downp.rawShape()).first);
ASSERT_EQ(downp.vertexCount(), downControl.vertexCount());
for(unsigned long i = 0; i < downControl.vertexCount(); i++) {
@ -297,7 +335,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight)
{20, 20} };
Arranger<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250));
Nester<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250));
auto groups = arrange(rects.begin(), rects.end());
@ -350,7 +388,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose)
Coord min_obj_distance = 5;
Arranger<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250),
Nester<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250),
min_obj_distance);
auto groups = arrange(rects.begin(), rects.end());
@ -401,7 +439,7 @@ R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
setX(v, getX(v)/SCALE);
rbin.setVertex(i, v);
}
out << ShapeLike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
out << shapelike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
for(Item& sh : r) {
Item tsh(sh.transformedShape());
for(unsigned i = 0; i < tsh.vertexCount(); i++) {
@ -410,7 +448,7 @@ R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
setX(v, getX(v)/SCALE);
tsh.setVertex(i, v);
}
out << ShapeLike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
out << shapelike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
}
out << "\n</svg>" << std::endl;
}
@ -664,7 +702,7 @@ std::vector<ItemPair> nfp_concave_testdata = {
}
};
template<NfpLevel lvl, Coord SCALE>
template<nfp::NfpLevel lvl, Coord SCALE>
void testNfp(const std::vector<ItemPair>& testdata) {
using namespace libnest2d;
@ -674,29 +712,33 @@ void testNfp(const std::vector<ItemPair>& testdata) {
auto& exportfun = exportSVG<SCALE, Box>;
auto onetest = [&](Item& orbiter, Item& stationary){
auto onetest = [&](Item& orbiter, Item& stationary, unsigned testidx){
testcase++;
orbiter.translate({210*SCALE, 0});
auto&& nfp = Nfp::noFitPolygon<lvl>(stationary.rawShape(),
auto&& nfp = nfp::noFitPolygon<lvl>(stationary.rawShape(),
orbiter.transformedShape());
strategies::correctNfpPosition(nfp, stationary, orbiter);
placers::correctNfpPosition(nfp, stationary, orbiter);
auto v = ShapeLike::isValid(nfp.first);
auto valid = shapelike::isValid(nfp.first);
if(!v.first) {
std::cout << v.second << std::endl;
}
/*Item infp(nfp.first);
if(!valid.first) {
std::cout << "test instance: " << testidx << " "
<< valid.second << std::endl;
std::vector<std::reference_wrapper<Item>> inp = {std::ref(infp)};
exportfun(inp, bin, testidx);
}*/
ASSERT_TRUE(v.first);
ASSERT_TRUE(valid.first);
Item infp(nfp.first);
int i = 0;
auto rorbiter = orbiter.transformedShape();
auto vo = Nfp::referenceVertex(rorbiter);
auto vo = nfp::referenceVertex(rorbiter);
ASSERT_TRUE(stationary.isInside(infp));
@ -710,7 +752,7 @@ void testNfp(const std::vector<ItemPair>& testdata) {
bool touching = Item::touches(tmp, stationary);
if(!touching) {
if(!touching || !valid.first) {
std::vector<std::reference_wrapper<Item>> inp = {
std::ref(stationary), std::ref(tmp), std::ref(infp)
};
@ -722,22 +764,24 @@ void testNfp(const std::vector<ItemPair>& testdata) {
}
};
unsigned tidx = 0;
for(auto& td : testdata) {
auto orbiter = td.orbiter;
auto stationary = td.stationary;
onetest(orbiter, stationary);
onetest(orbiter, stationary, tidx++);
}
tidx = 0;
for(auto& td : testdata) {
auto orbiter = td.stationary;
auto stationary = td.orbiter;
onetest(orbiter, stationary);
onetest(orbiter, stationary, tidx++);
}
}
}
TEST(GeometryAlgorithms, nfpConvexConvex) {
testNfp<NfpLevel::CONVEX_ONLY, 1>(nfp_testdata);
testNfp<nfp::NfpLevel::CONVEX_ONLY, 1>(nfp_testdata);
}
//TEST(GeometryAlgorithms, nfpConcaveConcave) {
@ -758,7 +802,7 @@ TEST(GeometryAlgorithms, pointOnPolygonContour) {
Rectangle input(10, 10);
strategies::EdgeCache<PolygonImpl> ecache(input);
placers::EdgeCache<PolygonImpl> ecache(input);
auto first = *input.begin();
ASSERT_TRUE(getX(first) == getX(ecache.coords(0)));
@ -770,7 +814,7 @@ TEST(GeometryAlgorithms, pointOnPolygonContour) {
for(int i = 0; i <= 100; i++) {
auto v = ecache.coords(i*(0.01));
ASSERT_TRUE(ShapeLike::touches(v, input.transformedShape()));
ASSERT_TRUE(shapelike::touches(v, input.transformedShape()));
}
}
@ -784,17 +828,17 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) {
rect2.translate({10, 0});
rect3.translate({25, 0});
ShapeLike::Shapes<PolygonImpl> pile;
shapelike::Shapes<PolygonImpl> pile;
pile.push_back(rect1.transformedShape());
pile.push_back(rect2.transformedShape());
auto result = Nfp::merge(pile, rect3.transformedShape());
auto result = nfp::merge(pile, rect3.transformedShape());
ASSERT_EQ(result.size(), 1);
Rectangle ref(45, 15);
ASSERT_EQ(ShapeLike::area(result.front()), ref.area());
ASSERT_EQ(shapelike::area(result.front()), ref.area());
}
int main(int argc, char **argv) {

View File

@ -56,7 +56,7 @@ libnfporb::point_t scale(const libnfporb::point_t& p, long double factor) {
NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
{
using Vertex = PointImpl;
namespace sl = shapelike;
NfpR ret;
@ -85,7 +85,7 @@ NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
// this can throw
auto nfp = libnfporb::generateNFP(pstat, porb, true);
auto &ct = ShapeLike::getContour(ret.first);
auto &ct = sl::getContour(ret.first);
ct.reserve(nfp.front().size()+1);
for(auto v : nfp.front()) {
v = scale(v, refactor);
@ -94,7 +94,7 @@ NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
ct.push_back(ct.front());
std::reverse(ct.begin(), ct.end());
auto &rholes = ShapeLike::holes(ret.first);
auto &rholes = sl::holes(ret.first);
for(size_t hidx = 1; hidx < nfp.size(); ++hidx) {
if(nfp[hidx].size() >= 3) {
rholes.emplace_back();
@ -110,31 +110,31 @@ NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
}
}
ret.second = Nfp::referenceVertex(ret.first);
ret.second = nfp::referenceVertex(ret.first);
} catch(std::exception& e) {
std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl;
// auto ch_stat = ShapeLike::convexHull(sh);
// auto ch_orb = ShapeLike::convexHull(cother);
ret = Nfp::nfpConvexOnly(sh, cother);
ret = nfp::nfpConvexOnly(sh, cother);
}
return ret;
}
NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY>::operator()(
NfpR nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::CONVEX_ONLY>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{
return _nfp(sh, cother);//nfpConvexOnly(sh, cother);
}
NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX>::operator()(
NfpR nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::ONE_CONVEX>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{
return _nfp(sh, cother);
}
NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE>::operator()(
NfpR nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::BOTH_CONCAVE>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{
return _nfp(sh, cother);

View File

@ -5,22 +5,22 @@
namespace libnest2d {
using NfpR = Nfp::NfpResult<PolygonImpl>;
using NfpR = nfp::NfpResult<PolygonImpl>;
NfpR _nfp(const PolygonImpl& sh, const PolygonImpl& cother);
template<>
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY> {
struct nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::CONVEX_ONLY> {
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
};
template<>
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX> {
struct nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::ONE_CONVEX> {
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
};
template<>
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE> {
struct nfp::NfpImpl<PolygonImpl, nfp::NfpLevel::BOTH_CONCAVE> {
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
};
@ -34,7 +34,7 @@ struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE> {
// NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother);
//};
template<> struct Nfp::MaxNfpLevel<PolygonImpl> {
template<> struct nfp::MaxNfpLevel<PolygonImpl> {
static const BP2D_CONSTEXPR NfpLevel value =
// NfpLevel::CONVEX_ONLY;
NfpLevel::BOTH_CONCAVE;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,75 @@
#ifndef NFP_SVGNEST_GLUE_HPP
#define NFP_SVGNEST_GLUE_HPP
#include "nfp_svgnest.hpp"
#include <libnest2d/clipper_backend/clipper_backend.hpp>
namespace libnest2d {
namespace __svgnest {
//template<> struct _Tol<double> {
// static const BP2D_CONSTEXPR TCoord<PointImpl> Value = 1000000;
//};
}
namespace nfp {
using NfpR = NfpResult<PolygonImpl>;
template<> struct NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY> {
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) {
// return nfpConvexOnly(sh, cother);
namespace sl = shapelike;
using alg = __svgnest::_alg<PolygonImpl>;
auto nfp_p = alg::noFitPolygon(sl::getContour(sh),
sl::getContour(cother), false, false);
PolygonImpl nfp_cntr;
if(!nfp_p.empty()) nfp_cntr.Contour = nfp_p.front();
return {nfp_cntr, referenceVertex(nfp_cntr)};
}
};
template<> struct NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX> {
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) {
// return nfpConvexOnly(sh, cother);
namespace sl = shapelike;
using alg = __svgnest::_alg<PolygonImpl>;
std::cout << "Itt vagyok" << std::endl;
auto nfp_p = alg::noFitPolygon(sl::getContour(sh),
sl::getContour(cother), false, false);
PolygonImpl nfp_cntr;
nfp_cntr.Contour = nfp_p.front();
return {nfp_cntr, referenceVertex(nfp_cntr)};
}
};
template<>
struct NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE> {
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother) {
namespace sl = shapelike;
using alg = __svgnest::_alg<PolygonImpl>;
auto nfp_p = alg::noFitPolygon(sl::getContour(sh),
sl::getContour(cother), true, false);
PolygonImpl nfp_cntr;
nfp_cntr.Contour = nfp_p.front();
return {nfp_cntr, referenceVertex(nfp_cntr)};
}
};
template<> struct MaxNfpLevel<PolygonImpl> {
// static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::BOTH_CONCAVE;
static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY;
};
}}
#endif // NFP_SVGNEST_GLUE_HPP

View File

@ -56,14 +56,14 @@ public:
auto d = static_cast<Coord>(
std::round(conf_.height*conf_.mm_in_coord_units) );
auto& contour = ShapeLike::getContour(tsh);
auto& contour = shapelike::getContour(tsh);
for(auto& v : contour) setY(v, -getY(v) + d);
auto& holes = ShapeLike::holes(tsh);
auto& holes = shapelike::holes(tsh);
for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d);
}
currentLayer() += ShapeLike::serialize<Formats::SVG>(tsh,
currentLayer() += shapelike::serialize<Formats::SVG>(tsh,
1.0/conf_.mm_in_coord_units) + "\n";
}

View File

@ -99,54 +99,55 @@ namespace bgi = boost::geometry::index;
using SpatElement = std::pair<Box, unsigned>;
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
template<class TBin>
using TPacker = typename placers::_NofitPolyPlacer<PolygonImpl, TBin>;
const double BIG_ITEM_TRESHOLD = 0.02;
Box boundingBox(const Box& pilebb, const Box& ibb ) {
auto& pminc = pilebb.minCorner();
auto& pmaxc = pilebb.maxCorner();
auto& iminc = ibb.minCorner();
auto& imaxc = ibb.maxCorner();
PointImpl minc, maxc;
setX(minc, std::min(getX(pminc), getX(iminc)));
setY(minc, std::min(getY(pminc), getY(iminc)));
setX(maxc, std::max(getX(pmaxc), getX(imaxc)));
setY(maxc, std::max(getY(pmaxc), getY(imaxc)));
return Box(minc, maxc);
}
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
objfunc(const PointImpl& bincenter,
double /*bin_area*/,
ShapeLike::Shapes<PolygonImpl>& pile, // The currently arranged pile
double /*pile_area*/,
const shapelike::Shapes<PolygonImpl>& merged_pile,
const Box& pilebb,
const ItemGroup& items,
const Item &item,
double bin_area,
double norm, // A norming factor for physical dimensions
std::vector<double>& areacache, // pile item areas will be cached
// a spatial index to quickly get neighbors of the candidate item
SpatIndex& spatindex
const SpatIndex& spatindex,
const ItemGroup& remaining
)
{
using pl = PointLike;
using sl = ShapeLike;
using Coord = TCoord<PointImpl>;
static const double BIG_ITEM_TRESHOLD = 0.2;
static const double ROUNDNESS_RATIO = 0.5;
static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO;
// We will treat big items (compared to the print bed) differently
auto normarea = [norm](double area) { return std::sqrt(area)/norm; };
// If a new bin has been created:
if(pile.size() < areacache.size()) {
areacache.clear();
spatindex.clear();
}
// We must fill the caches:
int idx = 0;
for(auto& p : pile) {
if(idx == areacache.size()) {
areacache.emplace_back(sl::area(p));
if(normarea(areacache[idx]) > BIG_ITEM_TRESHOLD)
spatindex.insert({sl::boundingBox(p), idx});
}
idx++;
}
auto isBig = [bin_area](double a) {
return a/bin_area > BIG_ITEM_TRESHOLD ;
};
// Candidate item bounding box
auto ibb = item.boundingBox();
auto ibb = sl::boundingBox(item.transformedShape());
// Calculate the full bounding box of the pile with the candidate item
pile.emplace_back(item.transformedShape());
auto fullbb = ShapeLike::boundingBox(pile);
pile.pop_back();
auto fullbb = boundingBox(pilebb, ibb);
// The bounding box of the big items (they will accumulate in the center
// of the pile
@ -157,19 +158,11 @@ objfunc(const PointImpl& bincenter,
boost::geometry::convert(boostbb, bigbb);
}
// The size indicator of the candidate item. This is not the area,
// but almost...
double item_normarea = normarea(item.area());
// Will hold the resulting score
double score = 0;
if(item_normarea > BIG_ITEM_TRESHOLD) {
if(isBig(item.area())) {
// This branch is for the bigger items..
// Here we will use the closest point of the item bounding box to
// the already arranged pile. So not the bb center nor the a choosen
// corner but whichever is the closest to the center. This will
// prevent some unwanted strange arrangements.
auto minc = ibb.minCorner(); // bottom left corner
auto maxc = ibb.maxCorner(); // top right corner
@ -192,46 +185,62 @@ objfunc(const PointImpl& bincenter,
auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
// Density is the pack density: how big is the arranged pile
auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
double density = 0;
// Prepare a variable for the alignment score.
// This will indicate: how well is the candidate item aligned with
// its neighbors. We will check the aligment with all neighbors and
// return the score for the best alignment. So it is enough for the
// candidate to be aligned with only one item.
auto alignment_score = std::numeric_limits<double>::max();
if(remaining.empty()) {
auto& trsh = item.transformedShape();
auto mp = merged_pile;
mp.emplace_back(item.transformedShape());
auto chull = sl::convexHull(mp);
auto querybb = item.boundingBox();
placers::EdgeCache<PolygonImpl> ec(chull);
// Query the spatial index for the neigbours
std::vector<SpatElement> result;
spatindex.query(bgi::intersects(querybb), std::back_inserter(result));
double circ = ec.circumference() / norm;
double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm;
score = 0.5*circ + 0.5*bcirc;
for(auto& e : result) { // now get the score for the best alignment
auto idx = e.second;
auto& p = pile[idx];
auto parea = areacache[idx];
auto bb = sl::boundingBox(sl::Shapes<PolygonImpl>{p, trsh});
auto bbarea = bb.area();
auto ascore = 1.0 - (item.area() + parea)/bbarea;
} else {
// Prepare a variable for the alignment score.
// This will indicate: how well is the candidate item aligned with
// its neighbors. We will check the alignment with all neighbors and
// return the score for the best alignment. So it is enough for the
// candidate to be aligned with only one item.
auto alignment_score = 1.0;
if(ascore < alignment_score) alignment_score = ascore;
density = (fullbb.width()*fullbb.height()) / (norm*norm);
auto querybb = item.boundingBox();
// Query the spatial index for the neighbors
std::vector<SpatElement> result;
result.reserve(spatindex.size());
spatindex.query(bgi::intersects(querybb),
std::back_inserter(result));
for(auto& e : result) { // now get the score for the best alignment
auto idx = e.second;
Item& p = items[idx];
auto parea = p.area();
if(std::abs(1.0 - parea/item.area()) < 1e-6) {
auto bb = boundingBox(p.boundingBox(), ibb);
auto bbarea = bb.area();
auto ascore = 1.0 - (item.area() + parea)/bbarea;
if(ascore < alignment_score) alignment_score = ascore;
}
}
// The final mix of the score is the balance between the distance
// from the full pile center, the pack density and the
// alignment with the neighbors
if(result.empty())
score = 0.5 * dist + 0.5 * density;
else
score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score;
}
// The final mix of the score is the balance between the distance
// from the full pile center, the pack density and the
// alignment with the neigbours
auto C = 0.33;
score = C * dist + C * density + C * alignment_score;
} else if( item_normarea < BIG_ITEM_TRESHOLD && spatindex.empty()) {
// If there are no big items, only small, we should consider the
// density here as well to not get silly results
} else if( !isBig(item.area()) && spatindex.empty()) {
auto bindist = pl::distance(ibb.center(), bincenter) / norm;
auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
score = ROUNDNESS_RATIO * bindist + DENSITY_RATIO * density;
// Bindist is surprisingly enough...
score = bindist;
} else {
// Here there are the small items that should be placed around the
// already processed bigger items.
@ -259,7 +268,9 @@ void fillConfig(PConf& pcfg) {
// The accuracy of optimization.
// Goes from 0.0 to 1.0 and scales performance as well
pcfg.accuracy = 0.6f;
pcfg.accuracy = 0.65f;
pcfg.parallel = true;
}
template<class TBin>
@ -268,31 +279,62 @@ class AutoArranger {};
template<class TBin>
class _ArrBase {
protected:
using Placer = strategies::_NofitPolyPlacer<PolygonImpl, TBin>;
using Placer = TPacker<TBin>;
using Selector = FirstFitSelection;
using Packer = Arranger<Placer, Selector>;
using Packer = Nester<Placer, Selector>;
using PConfig = typename Packer::PlacementConfig;
using Distance = TCoord<PointImpl>;
using Pile = ShapeLike::Shapes<PolygonImpl>;
using Pile = sl::Shapes<PolygonImpl>;
Packer pck_;
PConfig pconf_; // Placement configuration
double bin_area_;
std::vector<double> areacache_;
SpatIndex rtree_;
double norm_;
Pile merged_pile_;
Box pilebb_;
ItemGroup remaining_;
ItemGroup items_;
public:
_ArrBase(const TBin& bin, Distance dist,
std::function<void(unsigned)> progressind):
pck_(bin, dist), bin_area_(ShapeLike::area<PolygonImpl>(bin))
pck_(bin, dist), bin_area_(sl::area(bin)),
norm_(std::sqrt(sl::area(bin)))
{
fillConfig(pconf_);
pconf_.before_packing =
[this](const Pile& merged_pile, // merged pile
const ItemGroup& items, // packed items
const ItemGroup& remaining) // future items to be packed
{
items_ = items;
merged_pile_ = merged_pile;
remaining_ = remaining;
pilebb_ = sl::boundingBox(merged_pile);
rtree_.clear();
// We will treat big items (compared to the print bed) differently
auto isBig = [this](double a) {
return a/bin_area_ > BIG_ITEM_TRESHOLD ;
};
for(unsigned idx = 0; idx < items.size(); ++idx) {
Item& itm = items[idx];
if(isBig(itm.area())) rtree_.insert({itm.boundingBox(), idx});
}
};
pck_.progressIndicator(progressind);
}
template<class...Args> inline IndexedPackGroup operator()(Args&&...args) {
areacache_.clear();
return pck_.arrangeIndexed(std::forward<Args>(args)...);
rtree_.clear();
return pck_.executeIndexed(std::forward<Args>(args)...);
}
};
@ -304,22 +346,69 @@ public:
std::function<void(unsigned)> progressind):
_ArrBase<Box>(bin, dist, progressind)
{
pconf_.object_function = [this, bin] (
Pile& pile,
const Item &item,
double pile_area,
double norm,
double /*penality*/) {
auto result = objfunc(bin.center(), bin_area_, pile,
pile_area, item, norm, areacache_, rtree_);
pconf_.object_function = [this, bin] (const Item &item) {
auto result = objfunc(bin.center(),
merged_pile_,
pilebb_,
items_,
item,
bin_area_,
norm_,
rtree_,
remaining_);
double score = std::get<0>(result);
auto& fullbb = std::get<1>(result);
auto wdiff = fullbb.width() - bin.width();
auto hdiff = fullbb.height() - bin.height();
if(wdiff > 0) score += std::pow(wdiff, 2) / norm;
if(hdiff > 0) score += std::pow(hdiff, 2) / norm;
double miss = Placer::overfit(fullbb, bin);
miss = miss > 0? miss : 0;
score += miss*miss;
return score;
};
pck_.configure(pconf_);
}
};
using lnCircle = libnest2d::_Circle<libnest2d::PointImpl>;
template<>
class AutoArranger<lnCircle>: public _ArrBase<lnCircle> {
public:
AutoArranger(const lnCircle& bin, Distance dist,
std::function<void(unsigned)> progressind):
_ArrBase<lnCircle>(bin, dist, progressind) {
pconf_.object_function = [this, &bin] (const Item &item) {
auto result = objfunc(bin.center(),
merged_pile_,
pilebb_,
items_,
item,
bin_area_,
norm_,
rtree_,
remaining_);
double score = std::get<0>(result);
auto isBig = [this](const Item& itm) {
return itm.area()/bin_area_ > BIG_ITEM_TRESHOLD ;
};
if(isBig(item)) {
auto mp = merged_pile_;
mp.push_back(item.transformedShape());
auto chull = sl::convexHull(mp);
double miss = Placer::overfit(chull, bin);
if(miss < 0) miss = 0;
score += miss*miss;
}
return score;
};
@ -335,27 +424,20 @@ public:
std::function<void(unsigned)> progressind):
_ArrBase<PolygonImpl>(bin, dist, progressind)
{
pconf_.object_function = [this, &bin] (
Pile& pile,
const Item &item,
double pile_area,
double norm,
double /*penality*/) {
pconf_.object_function = [this, &bin] (const Item &item) {
auto binbb = ShapeLike::boundingBox(bin);
auto result = objfunc(binbb.center(), bin_area_, pile,
pile_area, item, norm, areacache_, rtree_);
auto binbb = sl::boundingBox(bin);
auto result = objfunc(binbb.center(),
merged_pile_,
pilebb_,
items_,
item,
bin_area_,
norm_,
rtree_,
remaining_);
double score = std::get<0>(result);
pile.emplace_back(item.transformedShape());
auto chull = ShapeLike::convexHull(pile);
pile.pop_back();
// If it does not fit into the print bed we will beat it with a
// large penality. If we would not do this, there would be only one
// big pile that doesn't care whether it fits onto the print bed.
if(!Placer::wouldFit(chull, bin)) score += norm;
return score;
};
@ -370,15 +452,17 @@ public:
AutoArranger(Distance dist, std::function<void(unsigned)> progressind):
_ArrBase<Box>(Box(0, 0), dist, progressind)
{
this->pconf_.object_function = [this] (
Pile& pile,
const Item &item,
double pile_area,
double norm,
double /*penality*/) {
this->pconf_.object_function = [this] (const Item &item) {
auto result = objfunc({0, 0}, 0, pile, pile_area,
item, norm, areacache_, rtree_);
auto result = objfunc({0, 0},
merged_pile_,
pilebb_,
items_,
item,
0,
norm_,
rtree_,
remaining_);
return std::get<0>(result);
};
@ -440,16 +524,113 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
return ret;
}
enum BedShapeHint {
class Circle {
Point center_;
double radius_;
public:
inline Circle(): center_(0, 0), radius_(std::nan("")) {}
inline Circle(const Point& c, double r): center_(c), radius_(r) {}
inline double radius() const { return radius_; }
inline const Point& center() const { return center_; }
inline operator bool() { return !std::isnan(radius_); }
inline operator lnCircle() {
return lnCircle({center_(0), center_(1)}, radius_);
}
};
enum class BedShapeType {
BOX,
CIRCLE,
IRREGULAR,
WHO_KNOWS
};
BedShapeHint bedShape(const Slic3r::Polyline& /*bed*/) {
struct BedShapeHint {
BedShapeType type;
/*union*/ struct { // I know but who cares...
Circle circ;
BoundingBox box;
Polyline polygon;
} shape;
};
BedShapeHint bedShape(const Polyline& bed) {
BedShapeHint ret;
auto x = [](const Point& p) { return p(0); };
auto y = [](const Point& p) { return p(1); };
auto width = [x](const BoundingBox& box) {
return x(box.max) - x(box.min);
};
auto height = [y](const BoundingBox& box) {
return y(box.max) - y(box.min);
};
auto area = [&width, &height](const BoundingBox& box) {
double w = width(box);
double h = height(box);
return w*h;
};
auto poly_area = [](Polyline p) {
Polygon pp; pp.points.reserve(p.points.size() + 1);
pp.points = std::move(p.points);
pp.points.emplace_back(pp.points.front());
return std::abs(pp.area());
};
auto distance_to = [x, y](const Point& p1, const Point& p2) {
double dx = x(p2) - x(p1);
double dy = y(p2) - y(p1);
return std::sqrt(dx*dx + dy*dy);
};
auto bb = bed.bounding_box();
auto isCircle = [bb, distance_to](const Polyline& polygon) {
auto center = bb.center();
std::vector<double> vertex_distances;
double avg_dist = 0;
for (auto pt: polygon.points)
{
double distance = distance_to(center, pt);
vertex_distances.push_back(distance);
avg_dist += distance;
}
avg_dist /= vertex_distances.size();
Circle ret(center, avg_dist);
for (auto el: vertex_distances)
{
if (abs(el - avg_dist) > 10 * SCALED_EPSILON)
ret = Circle();
break;
}
return ret;
};
auto parea = poly_area(bed);
if( (1.0 - parea/area(bb)) < 1e-3 ) {
ret.type = BedShapeType::BOX;
ret.shape.box = bb;
}
else if(auto c = isCircle(bed)) {
ret.type = BedShapeType::CIRCLE;
ret.shape.circ = c;
} else {
ret.type = BedShapeType::IRREGULAR;
ret.shape.polygon = bed;
}
// Determine the bed shape by hand
return BOX;
return ret;
}
void applyResult(
@ -468,8 +649,7 @@ void applyResult(
// appropriately
auto off = item.translation();
Radians rot = item.rotation();
Vec2d foff(off.X*SCALING_FACTOR + batch_offset,
off.Y*SCALING_FACTOR);
Vec2d foff(off.X*SCALING_FACTOR + batch_offset, off.Y*SCALING_FACTOR);
// write the tranformation data into the model instance
inst_ptr->rotation = rot;
@ -525,7 +705,10 @@ bool arrange(Model &model, coordf_t min_obj_distance,
});
IndexedPackGroup result;
BoundingBox bbb(bed.points);
if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed);
BoundingBox bbb(bed);
auto binbb = Box({
static_cast<libnest2d::Coord>(bbb.min(0)),
@ -536,8 +719,8 @@ bool arrange(Model &model, coordf_t min_obj_distance,
static_cast<libnest2d::Coord>(bbb.max(1))
});
switch(bedhint) {
case BOX: {
switch(bedhint.type) {
case BedShapeType::BOX: {
// Create the arranger for the box shaped bed
AutoArranger<Box> arrange(binbb, min_obj_distance, progressind);
@ -547,16 +730,22 @@ bool arrange(Model &model, coordf_t min_obj_distance,
result = arrange(shapes.begin(), shapes.end());
break;
}
case CIRCLE:
case BedShapeType::CIRCLE: {
auto c = bedhint.shape.circ;
auto cc = lnCircle(c);
AutoArranger<lnCircle> arrange(cc, min_obj_distance, progressind);
result = arrange(shapes.begin(), shapes.end());
break;
case IRREGULAR:
case WHO_KNOWS: {
}
case BedShapeType::IRREGULAR:
case BedShapeType::WHO_KNOWS: {
using P = libnest2d::PolygonImpl;
auto ctour = Slic3rMultiPoint_to_ClipperPath(bed);
P irrbed = ShapeLike::create<PolygonImpl>(std::move(ctour));
// std::cout << ShapeLike::toString(irrbed) << std::endl;
P irrbed = sl::create<PolygonImpl>(std::move(ctour));
AutoArranger<P> arrange(irrbed, min_obj_distance, progressind);
@ -567,6 +756,8 @@ bool arrange(Model &model, coordf_t min_obj_distance,
}
};
if(result.empty()) return false;
if(first_bin_only) {
applyResult(result.front(), 0, shapemap);
} else {

View File

@ -433,6 +433,7 @@ const PrintConfig &PrintController::config() const
return print_->config;
}
void AppController::arrange_model()
{
using Coord = libnest2d::TCoord<libnest2d::PointImpl>;
@ -468,21 +469,25 @@ void AppController::arrange_model()
if(pind) pind->update(0, _(L("Arranging objects...")));
try {
arr::BedShapeHint hint;
// TODO: from Sasha from GUI
hint.type = arr::BedShapeType::WHO_KNOWS;
arr::arrange(*model_,
min_obj_distance,
bed,
arr::BOX,
false, // create many piles not just one pile
[this, pind, count](unsigned rem) {
min_obj_distance,
bed,
hint,
false, // create many piles not just one pile
[pind, count](unsigned rem) {
if(pind)
pind->update(count - rem, _(L("Arranging objects...")));
});
} catch(std::exception& e) {
std::cerr << e.what() << std::endl;
report_issue(IssueType::ERR,
_(L("Could not arrange model objects! "
"Some geometries may be invalid.")),
_(L("Exception occurred")));
_(L("Could not arrange model objects! "
"Some geometries may be invalid.")),
_(L("Exception occurred")));
}
// Restore previous max value