mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-05-12 14:38:10 +08:00
initial NFP method with convex polygons working.
This commit is contained in:
parent
fd829580e9
commit
34c850fa9d
@ -684,6 +684,7 @@ add_custom_target(pot
|
|||||||
# ##############################################################################
|
# ##############################################################################
|
||||||
|
|
||||||
set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d")
|
set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d")
|
||||||
|
set(LIBNEST2D_BUILD_SHARED_LIB OFF CACHE BOOL "Disable build of shared lib.")
|
||||||
|
|
||||||
if(LIBNEST2D_UNITTESTS)
|
if(LIBNEST2D_UNITTESTS)
|
||||||
# If we want the libnest2d unit tests we need to build and executable with
|
# If we want the libnest2d unit tests we need to build and executable with
|
||||||
@ -705,7 +706,7 @@ add_subdirectory(${LIBDIR}/libnest2d)
|
|||||||
target_include_directories(libslic3r PUBLIC BEFORE ${LIBNEST2D_INCLUDES})
|
target_include_directories(libslic3r PUBLIC BEFORE ${LIBNEST2D_INCLUDES})
|
||||||
|
|
||||||
# Add the binpack2d main sources and link them to libslic3r
|
# Add the binpack2d main sources and link them to libslic3r
|
||||||
target_link_libraries(libslic3r libnest2d)
|
target_link_libraries(libslic3r libnest2d_static)
|
||||||
# ##############################################################################
|
# ##############################################################################
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
58
xs/src/benchmark.h
Normal file
58
xs/src/benchmark.h
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Tamás Mészáros
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
#ifndef INCLUDE_BENCHMARK_H_
|
||||||
|
#define INCLUDE_BENCHMARK_H_
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <ratio>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for doing benchmarks.
|
||||||
|
*/
|
||||||
|
class Benchmark {
|
||||||
|
typedef std::chrono::high_resolution_clock Clock;
|
||||||
|
typedef Clock::duration Duration;
|
||||||
|
typedef Clock::time_point TimePoint;
|
||||||
|
|
||||||
|
TimePoint t1, t2;
|
||||||
|
Duration d;
|
||||||
|
|
||||||
|
inline double to_sec(Duration d) {
|
||||||
|
return d.count() * double(Duration::period::num) / Duration::period::den;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Measure time from the moment of this call.
|
||||||
|
*/
|
||||||
|
void start() { t1 = Clock::now(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Measure time to the moment of this call.
|
||||||
|
*/
|
||||||
|
void stop() { t2 = Clock::now(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the time elapsed between a start() end a stop() call.
|
||||||
|
* @return Returns the elapsed time in seconds.
|
||||||
|
*/
|
||||||
|
double getElapsedSec() { d = t2 - t1; return to_sec(d); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* INCLUDE_BENCHMARK_H_ */
|
@ -19,6 +19,9 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/)
|
|||||||
option(LIBNEST2D_UNITTESTS "If enabled, googletest framework will be downloaded
|
option(LIBNEST2D_UNITTESTS "If enabled, googletest framework will be downloaded
|
||||||
and the provided unit tests will be included in the build." OFF)
|
and the provided unit tests will be included in the build." OFF)
|
||||||
|
|
||||||
|
option(LIBNEST2D_BUILD_EXAMPLES "If enabled, examples will be built." OFF)
|
||||||
|
option(LIBNEST2D_BUILD_SHARED_LIB "Build shared library." ON)
|
||||||
|
|
||||||
#set(LIBNEST2D_GEOMETRIES_TARGET "" CACHE STRING
|
#set(LIBNEST2D_GEOMETRIES_TARGET "" CACHE STRING
|
||||||
# "Build libnest2d with geometry classes implemented by the chosen target.")
|
# "Build libnest2d with geometry classes implemented by the chosen target.")
|
||||||
|
|
||||||
@ -46,7 +49,7 @@ if((NOT LIBNEST2D_GEOMETRIES_TARGET) OR (LIBNEST2D_GEOMETRIES_TARGET STREQUAL ""
|
|||||||
message(STATUS "libnest2D backend is default")
|
message(STATUS "libnest2D backend is default")
|
||||||
|
|
||||||
if(NOT Boost_INCLUDE_DIRS_FOUND)
|
if(NOT Boost_INCLUDE_DIRS_FOUND)
|
||||||
find_package(Boost REQUIRED)
|
find_package(Boost 1.58 REQUIRED)
|
||||||
# TODO automatic download of boost geometry headers
|
# TODO automatic download of boost geometry headers
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -65,9 +68,19 @@ else()
|
|||||||
message(STATUS "Libnest2D backend is: ${LIBNEST2D_GEOMETRIES_TARGET}")
|
message(STATUS "Libnest2D backend is: ${LIBNEST2D_GEOMETRIES_TARGET}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library(libnest2d STATIC ${LIBNEST2D_SRCFILES} )
|
message(STATUS "clipper lib is: ${LIBNEST2D_GEOMETRIES_TARGET}")
|
||||||
target_link_libraries(libnest2d ${LIBNEST2D_GEOMETRIES_TARGET})
|
|
||||||
target_include_directories(libnest2d PUBLIC ${CMAKE_SOURCE_DIR})
|
add_library(libnest2d_static STATIC ${LIBNEST2D_SRCFILES} )
|
||||||
|
target_link_libraries(libnest2d_static PRIVATE ${LIBNEST2D_GEOMETRIES_TARGET})
|
||||||
|
target_include_directories(libnest2d_static PUBLIC ${CMAKE_SOURCE_DIR})
|
||||||
|
set_target_properties(libnest2d_static PROPERTIES PREFIX "")
|
||||||
|
|
||||||
|
if(LIBNEST2D_BUILD_SHARED_LIB)
|
||||||
|
add_library(libnest2d SHARED ${LIBNEST2D_SRCFILES} )
|
||||||
|
target_link_libraries(libnest2d PRIVATE ${LIBNEST2D_GEOMETRIES_TARGET})
|
||||||
|
target_include_directories(libnest2d PUBLIC ${CMAKE_SOURCE_DIR})
|
||||||
|
set_target_properties(libnest2d PROPERTIES PREFIX "")
|
||||||
|
endif()
|
||||||
|
|
||||||
set(LIBNEST2D_HEADERS ${CMAKE_CURRENT_SOURCE_DIR})
|
set(LIBNEST2D_HEADERS ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
@ -79,3 +92,12 @@ endif()
|
|||||||
if(LIBNEST2D_UNITTESTS)
|
if(LIBNEST2D_UNITTESTS)
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(LIBNEST2D_BUILD_EXAMPLES)
|
||||||
|
add_executable(example tests/main.cpp
|
||||||
|
tests/svgtools.hpp
|
||||||
|
tests/printer_parts.cpp
|
||||||
|
tests/printer_parts.h)
|
||||||
|
target_link_libraries(example libnest2d_static)
|
||||||
|
target_include_directories(example PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
endif()
|
||||||
|
@ -14,6 +14,8 @@ FIND_PATH(CLIPPER_INCLUDE_DIRS clipper.hpp
|
|||||||
$ENV{CLIPPER_PATH}/include/polyclipping/
|
$ENV{CLIPPER_PATH}/include/polyclipping/
|
||||||
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/include/
|
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/include/
|
||||||
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/include/polyclipping/
|
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/include/polyclipping/
|
||||||
|
${CMAKE_PREFIX_PATH}/include/polyclipping
|
||||||
|
${CMAKE_PREFIX_PATH}/include/
|
||||||
/opt/local/include/
|
/opt/local/include/
|
||||||
/opt/local/include/polyclipping/
|
/opt/local/include/polyclipping/
|
||||||
/usr/local/include/
|
/usr/local/include/
|
||||||
@ -29,6 +31,8 @@ FIND_LIBRARY(CLIPPER_LIBRARIES polyclipping
|
|||||||
$ENV{CLIPPER_PATH}/lib/polyclipping/
|
$ENV{CLIPPER_PATH}/lib/polyclipping/
|
||||||
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/lib/
|
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/lib/
|
||||||
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/lib/polyclipping/
|
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/lib/polyclipping/
|
||||||
|
${CMAKE_PREFIX_PATH}/lib/
|
||||||
|
${CMAKE_PREFIX_PATH}/lib/polyclipping/
|
||||||
/opt/local/lib/
|
/opt/local/lib/
|
||||||
/opt/local/lib/polyclipping/
|
/opt/local/lib/polyclipping/
|
||||||
/usr/local/lib/
|
/usr/local/lib/
|
||||||
|
@ -29,6 +29,7 @@ using FillerSelection = strategies::_FillerSelection<PolygonImpl>;
|
|||||||
using FirstFitSelection = strategies::_FirstFitSelection<PolygonImpl>;
|
using FirstFitSelection = strategies::_FirstFitSelection<PolygonImpl>;
|
||||||
using DJDHeuristic = strategies::_DJDHeuristic<PolygonImpl>;
|
using DJDHeuristic = strategies::_DJDHeuristic<PolygonImpl>;
|
||||||
|
|
||||||
|
using NfpPlacer = strategies::_NofitPolyPlacer<PolygonImpl>;
|
||||||
using BottomLeftPlacer = strategies::_BottomLeftPlacer<PolygonImpl>;
|
using BottomLeftPlacer = strategies::_BottomLeftPlacer<PolygonImpl>;
|
||||||
using NofitPolyPlacer = strategies::_NofitPolyPlacer<PolygonImpl>;
|
using NofitPolyPlacer = strategies::_NofitPolyPlacer<PolygonImpl>;
|
||||||
|
|
||||||
|
@ -25,12 +25,13 @@ using libnest2d::setX;
|
|||||||
using libnest2d::setY;
|
using libnest2d::setY;
|
||||||
using Box = libnest2d::_Box<PointImpl>;
|
using Box = libnest2d::_Box<PointImpl>;
|
||||||
using Segment = libnest2d::_Segment<PointImpl>;
|
using Segment = libnest2d::_Segment<PointImpl>;
|
||||||
|
using Shapes = libnest2d::Nfp::Shapes<PolygonImpl>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We have to make all the binpack2d geometry types available to boost. The real
|
* We have to make all the libnest2d geometry types available to boost. The real
|
||||||
* models of the geometries remain the same if a conforming model for binpack2d
|
* models of the geometries remain the same if a conforming model for libnest2d
|
||||||
* was defined by the library client. Boost is used only as an optional
|
* was defined by the library client. Boost is used only as an optional
|
||||||
* implementer of some algorithms that can be implemented by the model itself
|
* implementer of some algorithms that can be implemented by the model itself
|
||||||
* if a faster alternative exists.
|
* if a faster alternative exists.
|
||||||
@ -184,10 +185,10 @@ template<> struct indexed_access<bp2d::Segment, 1, 1> {
|
|||||||
|
|
||||||
|
|
||||||
/* ************************************************************************** */
|
/* ************************************************************************** */
|
||||||
/* Polygon concept adaptaion ************************************************ */
|
/* Polygon concept adaptation *********************************************** */
|
||||||
/* ************************************************************************** */
|
/* ************************************************************************** */
|
||||||
|
|
||||||
// Connversion between binpack2d::Orientation and order_selector ///////////////
|
// Connversion between libnest2d::Orientation and order_selector ///////////////
|
||||||
|
|
||||||
template<bp2d::Orientation> struct ToBoostOrienation {};
|
template<bp2d::Orientation> struct ToBoostOrienation {};
|
||||||
|
|
||||||
@ -269,17 +270,34 @@ struct interior_rings<bp2d::PolygonImpl> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* ************************************************************************** */
|
||||||
|
/* MultiPolygon concept adaptation ****************************************** */
|
||||||
|
/* ************************************************************************** */
|
||||||
|
|
||||||
|
template<> struct tag<bp2d::Shapes> {
|
||||||
|
using type = multi_polygon_tag;
|
||||||
|
};
|
||||||
|
|
||||||
} // traits
|
} // traits
|
||||||
} // geometry
|
} // geometry
|
||||||
|
|
||||||
// This is an addition to the ring implementation
|
// This is an addition to the ring implementation of Polygon concept
|
||||||
template<>
|
template<>
|
||||||
struct range_value<bp2d::PathImpl> {
|
struct range_value<bp2d::PathImpl> {
|
||||||
using type = bp2d::PointImpl;
|
using type = bp2d::PointImpl;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct range_value<bp2d::Shapes> {
|
||||||
|
using type = bp2d::PolygonImpl;
|
||||||
|
};
|
||||||
|
|
||||||
} // boost
|
} // boost
|
||||||
|
|
||||||
|
/* ************************************************************************** */
|
||||||
|
/* Algorithms *************************************************************** */
|
||||||
|
/* ************************************************************************** */
|
||||||
|
|
||||||
namespace libnest2d { // Now the algorithms that boost can provide...
|
namespace libnest2d { // Now the algorithms that boost can provide...
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
@ -296,7 +314,7 @@ inline double PointLike::distance(const PointImpl& p,
|
|||||||
return boost::geometry::distance(p, seg);
|
return boost::geometry::distance(p, seg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tell binpack2d how to make string out of a ClipperPolygon object
|
// Tell libnest2d how to make string out of a ClipperPolygon object
|
||||||
template<>
|
template<>
|
||||||
inline bool ShapeLike::intersects(const PathImpl& sh1,
|
inline bool ShapeLike::intersects(const PathImpl& sh1,
|
||||||
const PathImpl& sh2)
|
const PathImpl& sh2)
|
||||||
@ -304,14 +322,14 @@ inline bool ShapeLike::intersects(const PathImpl& sh1,
|
|||||||
return boost::geometry::intersects(sh1, sh2);
|
return boost::geometry::intersects(sh1, sh2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tell binpack2d how to make string out of a ClipperPolygon object
|
// Tell libnest2d how to make string out of a ClipperPolygon object
|
||||||
template<>
|
template<>
|
||||||
inline bool ShapeLike::intersects(const PolygonImpl& sh1,
|
inline bool ShapeLike::intersects(const PolygonImpl& sh1,
|
||||||
const PolygonImpl& sh2) {
|
const PolygonImpl& sh2) {
|
||||||
return boost::geometry::intersects(sh1, sh2);
|
return boost::geometry::intersects(sh1, sh2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tell binpack2d how to make string out of a ClipperPolygon object
|
// Tell libnest2d how to make string out of a ClipperPolygon object
|
||||||
template<>
|
template<>
|
||||||
inline bool ShapeLike::intersects(const bp2d::Segment& s1,
|
inline bool ShapeLike::intersects(const bp2d::Segment& s1,
|
||||||
const bp2d::Segment& s2) {
|
const bp2d::Segment& s2) {
|
||||||
@ -346,6 +364,7 @@ inline bool ShapeLike::touches( const PolygonImpl& sh1,
|
|||||||
return boost::geometry::touches(sh1, sh2);
|
return boost::geometry::touches(sh1, sh2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef DISABLE_BOOST_BOUNDING_BOX
|
||||||
template<>
|
template<>
|
||||||
inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh) {
|
inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh) {
|
||||||
bp2d::Box b;
|
bp2d::Box b;
|
||||||
@ -354,7 +373,34 @@ inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) {
|
inline bp2d::Box ShapeLike::boundingBox(const bp2d::Shapes& shapes) {
|
||||||
|
bp2d::Box b;
|
||||||
|
boost::geometry::envelope(shapes, b);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef DISABLE_BOOST_CONVEX_HULL
|
||||||
|
template<>
|
||||||
|
inline PolygonImpl ShapeLike::convexHull(const PolygonImpl& sh)
|
||||||
|
{
|
||||||
|
PolygonImpl ret;
|
||||||
|
boost::geometry::convex_hull(sh, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline PolygonImpl ShapeLike::convexHull(const bp2d::Shapes& shapes)
|
||||||
|
{
|
||||||
|
PolygonImpl ret;
|
||||||
|
boost::geometry::convex_hull(shapes, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads)
|
||||||
|
{
|
||||||
namespace trans = boost::geometry::strategy::transform;
|
namespace trans = boost::geometry::strategy::transform;
|
||||||
|
|
||||||
PolygonImpl cpy = sh;
|
PolygonImpl cpy = sh;
|
||||||
@ -364,8 +410,10 @@ inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) {
|
|||||||
boost::geometry::transform(cpy, sh, rotate);
|
boost::geometry::transform(cpy, sh, rotate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef DISABLE_BOOST_TRANSLATE
|
||||||
template<>
|
template<>
|
||||||
inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs) {
|
inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs)
|
||||||
|
{
|
||||||
namespace trans = boost::geometry::strategy::transform;
|
namespace trans = boost::geometry::strategy::transform;
|
||||||
|
|
||||||
PolygonImpl cpy = sh;
|
PolygonImpl cpy = sh;
|
||||||
@ -374,19 +422,33 @@ inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs) {
|
|||||||
|
|
||||||
boost::geometry::transform(cpy, sh, translate);
|
boost::geometry::transform(cpy, sh, translate);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef DISABLE_BOOST_OFFSET
|
#ifndef DISABLE_BOOST_OFFSET
|
||||||
template<>
|
template<>
|
||||||
inline void ShapeLike::offset(PolygonImpl& sh, bp2d::Coord distance) {
|
inline void ShapeLike::offset(PolygonImpl& sh, bp2d::Coord distance)
|
||||||
|
{
|
||||||
PolygonImpl cpy = sh;
|
PolygonImpl cpy = sh;
|
||||||
boost::geometry::buffer(cpy, sh, distance);
|
boost::geometry::buffer(cpy, sh, distance);
|
||||||
}
|
}
|
||||||
#endif
|
#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_MINKOWSKI_ADD
|
#ifndef DISABLE_BOOST_MINKOWSKI_ADD
|
||||||
template<>
|
template<>
|
||||||
inline PolygonImpl& Nfp::minkowskiAdd(PolygonImpl& sh,
|
inline PolygonImpl& Nfp::minkowskiAdd(PolygonImpl& sh,
|
||||||
const PolygonImpl& /*other*/) {
|
const PolygonImpl& /*other*/)
|
||||||
|
{
|
||||||
return sh;
|
return sh;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -394,12 +456,26 @@ inline PolygonImpl& Nfp::minkowskiAdd(PolygonImpl& sh,
|
|||||||
#ifndef DISABLE_BOOST_SERIALIZE
|
#ifndef DISABLE_BOOST_SERIALIZE
|
||||||
template<>
|
template<>
|
||||||
inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>(
|
inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>(
|
||||||
const PolygonImpl& sh)
|
const PolygonImpl& sh, double scale)
|
||||||
{
|
{
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
std::string style = "fill: orange; stroke: black; stroke-width: 1px;";
|
std::string style = "fill: none; stroke: black; stroke-width: 1px;";
|
||||||
auto svg_data = boost::geometry::svg(sh, style);
|
|
||||||
|
using namespace boost::geometry;
|
||||||
|
using Pointf = model::point<double, 2, cs::cartesian>;
|
||||||
|
using Polygonf = model::polygon<Pointf>;
|
||||||
|
|
||||||
|
Polygonf::ring_type ring;
|
||||||
|
ring.reserve(ShapeLike::contourVertexCount(sh));
|
||||||
|
|
||||||
|
for(auto it = ShapeLike::cbegin(sh); it != ShapeLike::cend(sh); it++) {
|
||||||
|
auto& v = *it;
|
||||||
|
ring.emplace_back(getX(v)*scale, getY(v)*scale);
|
||||||
|
};
|
||||||
|
Polygonf poly;
|
||||||
|
poly.outer() = ring;
|
||||||
|
auto svg_data = boost::geometry::svg(poly, style);
|
||||||
|
|
||||||
ss << svg_data << std::endl;
|
ss << svg_data << std::endl;
|
||||||
|
|
||||||
@ -417,7 +493,8 @@ inline void ShapeLike::unserialize<libnest2d::Formats::SVG>(
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
template<> inline std::pair<bool, std::string>
|
template<> inline std::pair<bool, std::string>
|
||||||
ShapeLike::isValid(const PolygonImpl& sh) {
|
ShapeLike::isValid(const PolygonImpl& sh)
|
||||||
|
{
|
||||||
std::string message;
|
std::string message;
|
||||||
bool ret = boost::geometry::is_valid(sh, message);
|
bool ret = boost::geometry::is_valid(sh, message);
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
if(NOT TARGET clipper) # If there is a clipper target in the parent project we are good to go.
|
if(NOT TARGET clipper) # If there is a clipper target in the parent project we are good to go.
|
||||||
|
|
||||||
find_package(Clipper QUIET)
|
find_package(Clipper 6.1)
|
||||||
|
|
||||||
if(NOT CLIPPER_FOUND)
|
if(NOT CLIPPER_FOUND)
|
||||||
find_package(Subversion QUIET)
|
find_package(Subversion QUIET)
|
||||||
|
@ -46,10 +46,25 @@ std::string ShapeLike::toString(const PolygonImpl& sh)
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> PolygonImpl ShapeLike::create( std::initializer_list< PointImpl > il)
|
template<> PolygonImpl ShapeLike::create( const PathImpl& path )
|
||||||
{
|
{
|
||||||
PolygonImpl p;
|
PolygonImpl p;
|
||||||
p.Contour = il;
|
p.Contour = path;
|
||||||
|
|
||||||
|
// Expecting that the coordinate system Y axis is positive in upwards
|
||||||
|
// direction
|
||||||
|
if(ClipperLib::Orientation(p.Contour)) {
|
||||||
|
// Not clockwise then reverse the b*tch
|
||||||
|
ClipperLib::ReversePath(p.Contour);
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> PolygonImpl ShapeLike::create( PathImpl&& path )
|
||||||
|
{
|
||||||
|
PolygonImpl p;
|
||||||
|
p.Contour.swap(path);
|
||||||
|
|
||||||
// Expecting that the coordinate system Y axis is positive in upwards
|
// Expecting that the coordinate system Y axis is positive in upwards
|
||||||
// direction
|
// direction
|
||||||
|
@ -20,6 +20,7 @@ using PolygonImpl = ClipperLib::PolyNode;
|
|||||||
using PathImpl = ClipperLib::Path;
|
using PathImpl = ClipperLib::Path;
|
||||||
|
|
||||||
inline PointImpl& operator +=(PointImpl& p, const PointImpl& pa ) {
|
inline PointImpl& operator +=(PointImpl& p, const PointImpl& pa ) {
|
||||||
|
// This could be done with SIMD
|
||||||
p.X += pa.X;
|
p.X += pa.X;
|
||||||
p.Y += pa.Y;
|
p.Y += pa.Y;
|
||||||
return p;
|
return p;
|
||||||
@ -37,6 +38,13 @@ inline PointImpl& operator -=(PointImpl& p, const PointImpl& pa ) {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline PointImpl operator -(PointImpl& p ) {
|
||||||
|
PointImpl ret = p;
|
||||||
|
ret.X = -ret.X;
|
||||||
|
ret.Y = -ret.Y;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
inline PointImpl operator-(const PointImpl& p1, const PointImpl& p2) {
|
inline PointImpl operator-(const PointImpl& p1, const PointImpl& p2) {
|
||||||
PointImpl ret = p1;
|
PointImpl ret = p1;
|
||||||
ret -= p2;
|
ret -= p2;
|
||||||
@ -100,14 +108,29 @@ inline void ShapeLike::reserve(PolygonImpl& sh, unsigned long vertex_capacity)
|
|||||||
return sh.Contour.reserve(vertex_capacity);
|
return sh.Contour.reserve(vertex_capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define DISABLE_BOOST_AREA
|
||||||
|
|
||||||
|
namespace _smartarea {
|
||||||
|
template<Orientation o>
|
||||||
|
inline double area(const PolygonImpl& sh) {
|
||||||
|
return std::nan("");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline double area<Orientation::CLOCKWISE>(const PolygonImpl& sh) {
|
||||||
|
return -ClipperLib::Area(sh.Contour);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline double area<Orientation::COUNTER_CLOCKWISE>(const PolygonImpl& sh) {
|
||||||
|
return ClipperLib::Area(sh.Contour);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Tell binpack2d how to make string out of a ClipperPolygon object
|
// Tell binpack2d how to make string out of a ClipperPolygon object
|
||||||
template<>
|
template<>
|
||||||
inline double ShapeLike::area(const PolygonImpl& sh) {
|
inline double ShapeLike::area(const PolygonImpl& sh) {
|
||||||
#define DISABLE_BOOST_AREA
|
return _smartarea::area<OrientationType<PolygonImpl>::Value>(sh);
|
||||||
double ret = ClipperLib::Area(sh.Contour);
|
|
||||||
// if(OrientationType<PolygonImpl>::Value == Orientation::COUNTER_CLOCKWISE)
|
|
||||||
// ret = -ret;
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
@ -191,8 +214,9 @@ template<> struct HolesContainer<PolygonImpl> {
|
|||||||
using Type = ClipperLib::Paths;
|
using Type = ClipperLib::Paths;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<>
|
template<> PolygonImpl ShapeLike::create( const PathImpl& path);
|
||||||
PolygonImpl ShapeLike::create( std::initializer_list< PointImpl > il);
|
|
||||||
|
template<> PolygonImpl ShapeLike::create( PathImpl&& path);
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
const THolesContainer<PolygonImpl>& ShapeLike::holes(
|
const THolesContainer<PolygonImpl>& ShapeLike::holes(
|
||||||
@ -202,33 +226,94 @@ template<>
|
|||||||
THolesContainer<PolygonImpl>& ShapeLike::holes(PolygonImpl& sh);
|
THolesContainer<PolygonImpl>& ShapeLike::holes(PolygonImpl& sh);
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
inline TCountour<PolygonImpl>& ShapeLike::getHole(PolygonImpl& sh,
|
inline TContour<PolygonImpl>& ShapeLike::getHole(PolygonImpl& sh,
|
||||||
unsigned long idx)
|
unsigned long idx)
|
||||||
{
|
{
|
||||||
return sh.Childs[idx]->Contour;
|
return sh.Childs[idx]->Contour;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
inline const TCountour<PolygonImpl>& ShapeLike::getHole(const PolygonImpl& sh,
|
inline const TContour<PolygonImpl>& ShapeLike::getHole(const PolygonImpl& sh,
|
||||||
unsigned long idx) {
|
unsigned long idx)
|
||||||
|
{
|
||||||
return sh.Childs[idx]->Contour;
|
return sh.Childs[idx]->Contour;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<> inline size_t ShapeLike::holeCount(const PolygonImpl& sh)
|
||||||
inline size_t ShapeLike::holeCount(const PolygonImpl& sh) {
|
{
|
||||||
return sh.Childs.size();
|
return sh.Childs.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<> inline PathImpl& ShapeLike::getContour(PolygonImpl& sh)
|
||||||
inline PathImpl& ShapeLike::getContour(PolygonImpl& sh) {
|
{
|
||||||
return sh.Contour;
|
return sh.Contour;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
inline const PathImpl& ShapeLike::getContour(const PolygonImpl& sh) {
|
inline const PathImpl& ShapeLike::getContour(const PolygonImpl& sh)
|
||||||
|
{
|
||||||
return sh.Contour;
|
return sh.Contour;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define DISABLE_BOOST_TRANSLATE
|
||||||
|
template<>
|
||||||
|
inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs)
|
||||||
|
{
|
||||||
|
for(auto& p : sh.Contour) { p += offs; }
|
||||||
|
for(auto& hole : sh.Childs) for(auto& p : hole->Contour) { p += offs; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DISABLE_BOOST_NFP_MERGE
|
||||||
|
|
||||||
|
template<> inline
|
||||||
|
Nfp::Shapes<PolygonImpl> Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes,
|
||||||
|
const PolygonImpl& sh)
|
||||||
|
{
|
||||||
|
Nfp::Shapes<PolygonImpl> retv;
|
||||||
|
|
||||||
|
ClipperLib::Clipper clipper;
|
||||||
|
|
||||||
|
bool closed = true;
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
#define _valid() valid =
|
||||||
|
bool valid = false;
|
||||||
|
#else
|
||||||
|
#define _valid()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_valid() clipper.AddPath(sh.Contour, ClipperLib::ptSubject, closed);
|
||||||
|
|
||||||
|
for(auto& hole : sh.Childs) {
|
||||||
|
_valid() clipper.AddPath(hole->Contour, ClipperLib::ptSubject, closed);
|
||||||
|
assert(valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& path : shapes) {
|
||||||
|
_valid() clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
|
||||||
|
assert(valid);
|
||||||
|
for(auto& hole : path.Childs) {
|
||||||
|
_valid() clipper.AddPath(hole->Contour, ClipperLib::ptSubject, closed);
|
||||||
|
assert(valid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipperLib::Paths rret;
|
||||||
|
clipper.Execute(ClipperLib::ctUnion, rret, ClipperLib::pftNonZero);
|
||||||
|
retv.reserve(rret.size());
|
||||||
|
for(auto& p : rret) {
|
||||||
|
if(ClipperLib::Orientation(p)) {
|
||||||
|
// Not clockwise then reverse the b*tch
|
||||||
|
ClipperLib::ReversePath(p);
|
||||||
|
}
|
||||||
|
retv.emplace_back();
|
||||||
|
retv.back().Contour = p;
|
||||||
|
retv.back().Contour.emplace_back(p.front());
|
||||||
|
}
|
||||||
|
|
||||||
|
return retv;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//#define DISABLE_BOOST_SERIALIZE
|
//#define DISABLE_BOOST_SERIALIZE
|
||||||
|
@ -3,18 +3,56 @@
|
|||||||
|
|
||||||
#include "geometry_traits.hpp"
|
#include "geometry_traits.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace libnest2d {
|
namespace libnest2d {
|
||||||
|
|
||||||
struct Nfp {
|
struct Nfp {
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
static RawShape& minkowskiAdd(RawShape& sh, const RawShape& /*other*/) {
|
using Shapes = typename ShapeLike::Shapes<RawShape>;
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
static RawShape& minkowskiAdd(RawShape& sh, const RawShape& /*other*/)
|
||||||
|
{
|
||||||
static_assert(always_false<RawShape>::value,
|
static_assert(always_false<RawShape>::value,
|
||||||
"ShapeLike::minkowskiAdd() unimplemented!");
|
"Nfp::minkowskiAdd() unimplemented!");
|
||||||
return sh;
|
return sh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
static Shapes<RawShape> merge(const Shapes<RawShape>& shc, const RawShape& sh)
|
||||||
|
{
|
||||||
|
static_assert(always_false<RawShape>::value,
|
||||||
|
"Nfp::merge(shapes, shape) unimplemented!");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
inline static TPoint<RawShape> referenceVertex(const RawShape& sh)
|
||||||
|
{
|
||||||
|
return rightmostUpVertex(sh);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
static TPoint<RawShape> rightmostUpVertex(const RawShape& sh) {
|
||||||
|
|
||||||
|
// find min x and min y vertex
|
||||||
|
auto it = std::max_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh),
|
||||||
|
_vsort<RawShape>);
|
||||||
|
|
||||||
|
return *it;
|
||||||
|
}
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) {
|
static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) {
|
||||||
auto isConvex = [](const RawShape& sh) {
|
auto isConvex = [](const RawShape& sh) {
|
||||||
@ -31,24 +69,20 @@ static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) {
|
|||||||
{
|
{
|
||||||
RawShape other = cother;
|
RawShape other = cother;
|
||||||
|
|
||||||
// Make it counter-clockwise
|
// Make the other polygon counter-clockwise
|
||||||
for(auto shit = ShapeLike::begin(other);
|
std::reverse(ShapeLike::begin(other), ShapeLike::end(other));
|
||||||
shit != ShapeLike::end(other); ++shit ) {
|
|
||||||
auto& v = *shit;
|
|
||||||
setX(v, -getX(v));
|
|
||||||
setY(v, -getY(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
RawShape rsh;
|
RawShape rsh; // Final nfp placeholder
|
||||||
std::vector<Edge> edgelist;
|
std::vector<Edge> edgelist;
|
||||||
|
|
||||||
size_t cap = ShapeLike::contourVertexCount(sh) +
|
size_t cap = ShapeLike::contourVertexCount(sh) +
|
||||||
ShapeLike::contourVertexCount(other);
|
ShapeLike::contourVertexCount(other);
|
||||||
|
|
||||||
|
// Reserve the needed memory
|
||||||
edgelist.reserve(cap);
|
edgelist.reserve(cap);
|
||||||
ShapeLike::reserve(rsh, cap);
|
ShapeLike::reserve(rsh, cap);
|
||||||
|
|
||||||
{
|
{ // place all edges from sh into edgelist
|
||||||
auto first = ShapeLike::cbegin(sh);
|
auto first = ShapeLike::cbegin(sh);
|
||||||
auto next = first + 1;
|
auto next = first + 1;
|
||||||
auto endit = ShapeLike::cend(sh);
|
auto endit = ShapeLike::cend(sh);
|
||||||
@ -56,7 +90,7 @@ static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) {
|
|||||||
while(next != endit) edgelist.emplace_back(*(first++), *(next++));
|
while(next != endit) edgelist.emplace_back(*(first++), *(next++));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{ // place all edges from other into edgelist
|
||||||
auto first = ShapeLike::cbegin(other);
|
auto first = ShapeLike::cbegin(other);
|
||||||
auto next = first + 1;
|
auto next = first + 1;
|
||||||
auto endit = ShapeLike::cend(other);
|
auto endit = ShapeLike::cend(other);
|
||||||
@ -64,31 +98,64 @@ static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) {
|
|||||||
while(next != endit) edgelist.emplace_back(*(first++), *(next++));
|
while(next != endit) edgelist.emplace_back(*(first++), *(next++));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort the edges by angle to X axis.
|
||||||
std::sort(edgelist.begin(), edgelist.end(),
|
std::sort(edgelist.begin(), edgelist.end(),
|
||||||
[](const Edge& e1, const Edge& e2)
|
[](const Edge& e1, const Edge& e2)
|
||||||
{
|
{
|
||||||
return e1.angleToXaxis() > e2.angleToXaxis();
|
return e1.angleToXaxis() > e2.angleToXaxis();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add the two vertices from the first edge into the final polygon.
|
||||||
ShapeLike::addVertex(rsh, edgelist.front().first());
|
ShapeLike::addVertex(rsh, edgelist.front().first());
|
||||||
ShapeLike::addVertex(rsh, edgelist.front().second());
|
ShapeLike::addVertex(rsh, edgelist.front().second());
|
||||||
|
|
||||||
auto tmp = std::next(ShapeLike::begin(rsh));
|
auto tmp = std::next(ShapeLike::begin(rsh));
|
||||||
|
|
||||||
// Construct final nfp
|
// Construct final nfp by placing each edge to the end of the previous
|
||||||
for(auto eit = std::next(edgelist.begin());
|
for(auto eit = std::next(edgelist.begin());
|
||||||
eit != edgelist.end();
|
eit != edgelist.end();
|
||||||
++eit) {
|
++eit)
|
||||||
|
{
|
||||||
|
auto d = *tmp - eit->first();
|
||||||
|
auto p = eit->second() + d;
|
||||||
|
|
||||||
auto dx = getX(*tmp) - getX(eit->first());
|
ShapeLike::addVertex(rsh, p);
|
||||||
auto dy = getY(*tmp) - getY(eit->first());
|
|
||||||
|
|
||||||
ShapeLike::addVertex(rsh, getX(eit->second())+dx,
|
|
||||||
getY(eit->second())+dy );
|
|
||||||
|
|
||||||
tmp = std::next(tmp);
|
tmp = std::next(tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now we have an nfp somewhere in the dark. We need to get it
|
||||||
|
// to the right position around the stationary shape.
|
||||||
|
// This is done by choosing the leftmost lowest vertex of the
|
||||||
|
// orbiting polygon to be touched with the rightmost upper
|
||||||
|
// vertex of the stationary polygon. In this configuration, the
|
||||||
|
// reference vertex of the orbiting polygon (which can be dragged around
|
||||||
|
// the nfp) will be its rightmost upper vertex that coincides with the
|
||||||
|
// rightmost upper vertex of the nfp. No proof provided other than Jonas
|
||||||
|
// Lindmark's reasoning about the reference vertex of nfp in his thesis
|
||||||
|
// ("No fit polygon problem" - section 2.1.9)
|
||||||
|
|
||||||
|
auto csh = sh; // Copy sh, we will sort the verices in the copy
|
||||||
|
auto& cmp = _vsort<RawShape>;
|
||||||
|
std::sort(ShapeLike::begin(csh), ShapeLike::end(csh), cmp);
|
||||||
|
std::sort(ShapeLike::begin(other), ShapeLike::end(other), cmp);
|
||||||
|
|
||||||
|
// leftmost lower vertex of the stationary polygon
|
||||||
|
auto& touch_sh = *(std::prev(ShapeLike::end(csh)));
|
||||||
|
// rightmost upper vertex of the orbiting polygon
|
||||||
|
auto& touch_other = *(ShapeLike::begin(other));
|
||||||
|
|
||||||
|
// Calculate the difference and move the orbiter to the touch position.
|
||||||
|
auto dtouch = touch_sh - touch_other;
|
||||||
|
auto top_other = *(std::prev(ShapeLike::end(other))) + dtouch;
|
||||||
|
|
||||||
|
// Get the righmost upper vertex of the nfp and move it to the RMU of
|
||||||
|
// the orbiter because they should coincide.
|
||||||
|
auto&& top_nfp = rightmostUpVertex(rsh);
|
||||||
|
auto dnfp = top_other - top_nfp;
|
||||||
|
std::for_each(ShapeLike::begin(rsh), ShapeLike::end(rsh),
|
||||||
|
[&dnfp](Vertex& v) { v+= dnfp; } );
|
||||||
|
|
||||||
return rsh;
|
return rsh;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -118,6 +185,36 @@ static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) {
|
|||||||
return rsh;
|
return rsh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
static inline Shapes<RawShape> noFitPolygon(const Shapes<RawShape>& shapes,
|
||||||
|
const RawShape& other)
|
||||||
|
{
|
||||||
|
assert(shapes.size() >= 1);
|
||||||
|
auto shit = shapes.begin();
|
||||||
|
|
||||||
|
Shapes<RawShape> ret;
|
||||||
|
ret.emplace_back(noFitPolygon(*shit, other));
|
||||||
|
|
||||||
|
while(++shit != shapes.end()) ret = merge(ret, noFitPolygon(*shit, other));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// Do not specialize this...
|
||||||
|
template<class RawShape>
|
||||||
|
static inline bool _vsort(const TPoint<RawShape>& v1,
|
||||||
|
const TPoint<RawShape>& v2)
|
||||||
|
{
|
||||||
|
using Coord = TCoord<TPoint<RawShape>>;
|
||||||
|
auto diff = getY(v1) - getY(v2);
|
||||||
|
if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
|
||||||
|
return getX(v1) < getX(v2);
|
||||||
|
|
||||||
|
return diff < 0;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <numeric>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
#include "common.hpp"
|
#include "common.hpp"
|
||||||
@ -81,6 +82,8 @@ public:
|
|||||||
|
|
||||||
inline TCoord<RawPoint> width() const BP2D_NOEXCEPT;
|
inline TCoord<RawPoint> width() const BP2D_NOEXCEPT;
|
||||||
inline TCoord<RawPoint> height() const BP2D_NOEXCEPT;
|
inline TCoord<RawPoint> height() const BP2D_NOEXCEPT;
|
||||||
|
|
||||||
|
inline RawPoint center() const BP2D_NOEXCEPT;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class RawPoint>
|
template<class RawPoint>
|
||||||
@ -102,8 +105,9 @@ public:
|
|||||||
inline Radians angleToXaxis() const;
|
inline Radians angleToXaxis() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PointLike {
|
// This struct serves as a namespace. The only difference is that is can be
|
||||||
public:
|
// used in friend declarations.
|
||||||
|
struct PointLike {
|
||||||
|
|
||||||
template<class RawPoint>
|
template<class RawPoint>
|
||||||
static TCoord<RawPoint> x(const RawPoint& p)
|
static TCoord<RawPoint> x(const RawPoint& p)
|
||||||
@ -133,7 +137,7 @@ public:
|
|||||||
static double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/)
|
static double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/)
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawPoint>::value,
|
static_assert(always_false<RawPoint>::value,
|
||||||
"PointLike::distance(point, point) unimplemented");
|
"PointLike::distance(point, point) unimplemented!");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +146,7 @@ public:
|
|||||||
const _Segment<RawPoint>& /*s*/)
|
const _Segment<RawPoint>& /*s*/)
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawPoint>::value,
|
static_assert(always_false<RawPoint>::value,
|
||||||
"PointLike::distance(point, segment) unimplemented");
|
"PointLike::distance(point, segment) unimplemented!");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,21 +233,38 @@ void setY(RawPoint& p, const TCoord<RawPoint>& val) {
|
|||||||
template<class RawPoint>
|
template<class RawPoint>
|
||||||
inline Radians _Segment<RawPoint>::angleToXaxis() const
|
inline Radians _Segment<RawPoint>::angleToXaxis() const
|
||||||
{
|
{
|
||||||
|
static const double Pi_2 = 2*Pi;
|
||||||
TCoord<RawPoint> dx = getX(second()) - getX(first());
|
TCoord<RawPoint> dx = getX(second()) - getX(first());
|
||||||
TCoord<RawPoint> dy = getY(second()) - getY(first());
|
TCoord<RawPoint> dy = getY(second()) - getY(first());
|
||||||
|
|
||||||
if(dx == 0 && dy >= 0) return Pi/2;
|
double a = std::atan2(dy, dx);
|
||||||
if(dx == 0 && dy < 0) return 3*Pi/2;
|
// if(dx == 0 && dy >= 0) return Pi/2;
|
||||||
if(dy == 0 && dx >= 0) return 0;
|
// if(dx == 0 && dy < 0) return 3*Pi/2;
|
||||||
if(dy == 0 && dx < 0) return Pi;
|
// if(dy == 0 && dx >= 0) return 0;
|
||||||
|
// if(dy == 0 && dx < 0) return Pi;
|
||||||
|
|
||||||
double ddx = static_cast<double>(dx);
|
// double ddx = static_cast<double>(dx);
|
||||||
auto s = std::signbit(ddx);
|
auto s = std::signbit(a);
|
||||||
double a = std::atan(ddx/dy);
|
// double a = std::atan(ddx/dy);
|
||||||
if(s) a += Pi;
|
if(s) a += Pi_2;
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class RawPoint>
|
||||||
|
inline RawPoint _Box<RawPoint>::center() const BP2D_NOEXCEPT {
|
||||||
|
auto& minc = minCorner();
|
||||||
|
auto& maxc = maxCorner();
|
||||||
|
|
||||||
|
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) )
|
||||||
|
};
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
struct HolesContainer {
|
struct HolesContainer {
|
||||||
using Type = std::vector<RawShape>;
|
using Type = std::vector<RawShape>;
|
||||||
@ -258,7 +279,7 @@ struct CountourType {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
using TCountour = typename CountourType<remove_cvref_t<RawShape>>::Type;
|
using TContour = typename CountourType<remove_cvref_t<RawShape>>::Type;
|
||||||
|
|
||||||
enum class Orientation {
|
enum class Orientation {
|
||||||
CLOCKWISE,
|
CLOCKWISE,
|
||||||
@ -277,12 +298,23 @@ enum class Formats {
|
|||||||
SVG
|
SVG
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This struct serves as a namespace. The only difference is that is can be
|
||||||
|
// used in friend declarations.
|
||||||
struct ShapeLike {
|
struct ShapeLike {
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
static RawShape create( std::initializer_list< TPoint<RawShape> > il)
|
using Shapes = std::vector<RawShape>;
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
static RawShape create(const TContour<RawShape>& contour)
|
||||||
{
|
{
|
||||||
return RawShape(il);
|
return RawShape(contour);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
static RawShape create(TContour<RawShape>&& contour)
|
||||||
|
{
|
||||||
|
return RawShape(contour);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional, does nothing by default
|
// Optional, does nothing by default
|
||||||
@ -319,25 +351,6 @@ struct ShapeLike {
|
|||||||
return sh.cend();
|
return sh.cend();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawShape>
|
|
||||||
static TPoint<RawShape>& vertex(RawShape& sh, unsigned long idx)
|
|
||||||
{
|
|
||||||
return *(begin(sh) + idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class RawShape>
|
|
||||||
static const TPoint<RawShape>& vertex(const RawShape& sh,
|
|
||||||
unsigned long idx)
|
|
||||||
{
|
|
||||||
return *(cbegin(sh) + idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class RawShape>
|
|
||||||
static size_t contourVertexCount(const RawShape& sh)
|
|
||||||
{
|
|
||||||
return cend(sh) - cbegin(sh);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
static std::string toString(const RawShape& /*sh*/)
|
static std::string toString(const RawShape& /*sh*/)
|
||||||
{
|
{
|
||||||
@ -345,10 +358,10 @@ struct ShapeLike {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<Formats, class RawShape>
|
template<Formats, class RawShape>
|
||||||
static std::string serialize(const RawShape& /*sh*/)
|
static std::string serialize(const RawShape& /*sh*/, double scale=1)
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawShape>::value,
|
static_assert(always_false<RawShape>::value,
|
||||||
"ShapeLike::serialize() unimplemented");
|
"ShapeLike::serialize() unimplemented!");
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,21 +369,14 @@ struct ShapeLike {
|
|||||||
static void unserialize(RawShape& /*sh*/, const std::string& /*str*/)
|
static void unserialize(RawShape& /*sh*/, const std::string& /*str*/)
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawShape>::value,
|
static_assert(always_false<RawShape>::value,
|
||||||
"ShapeLike::unserialize() unimplemented");
|
"ShapeLike::unserialize() unimplemented!");
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
static double area(const RawShape& /*sh*/)
|
static double area(const RawShape& /*sh*/)
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawShape>::value,
|
static_assert(always_false<RawShape>::value,
|
||||||
"ShapeLike::area() unimplemented");
|
"ShapeLike::area() unimplemented!");
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class RawShape>
|
|
||||||
static double area(const _Box<TPoint<RawShape>>& box)
|
|
||||||
{
|
|
||||||
return box.width() * box.height();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,7 +384,7 @@ struct ShapeLike {
|
|||||||
static bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/)
|
static bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/)
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawShape>::value,
|
static_assert(always_false<RawShape>::value,
|
||||||
"ShapeLike::intersects() unimplemented");
|
"ShapeLike::intersects() unimplemented!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,7 +393,7 @@ struct ShapeLike {
|
|||||||
const RawShape& /*shape*/)
|
const RawShape& /*shape*/)
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawShape>::value,
|
static_assert(always_false<RawShape>::value,
|
||||||
"ShapeLike::isInside(point, shape) unimplemented");
|
"ShapeLike::isInside(point, shape) unimplemented!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,7 +402,7 @@ struct ShapeLike {
|
|||||||
const RawShape& /*shape*/)
|
const RawShape& /*shape*/)
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawShape>::value,
|
static_assert(always_false<RawShape>::value,
|
||||||
"ShapeLike::isInside(shape, shape) unimplemented");
|
"ShapeLike::isInside(shape, shape) unimplemented!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,7 +411,7 @@ struct ShapeLike {
|
|||||||
const RawShape& /*shape*/)
|
const RawShape& /*shape*/)
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawShape>::value,
|
static_assert(always_false<RawShape>::value,
|
||||||
"ShapeLike::touches(shape, shape) unimplemented");
|
"ShapeLike::touches(shape, shape) unimplemented!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -413,13 +419,30 @@ struct ShapeLike {
|
|||||||
static _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/)
|
static _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/)
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawShape>::value,
|
static_assert(always_false<RawShape>::value,
|
||||||
"ShapeLike::boundingBox(shape) unimplemented");
|
"ShapeLike::boundingBox(shape) unimplemented!");
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
static _Box<TPoint<RawShape>> boundingBox(const _Box<TPoint<RawShape>>& box)
|
static _Box<TPoint<RawShape>> boundingBox(const Shapes<RawShape>& /*sh*/)
|
||||||
{
|
{
|
||||||
return box;
|
static_assert(always_false<RawShape>::value,
|
||||||
|
"ShapeLike::boundingBox(shapes) unimplemented!");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
static RawShape convexHull(const RawShape& /*sh*/)
|
||||||
|
{
|
||||||
|
static_assert(always_false<RawShape>::value,
|
||||||
|
"ShapeLike::convexHull(shape) unimplemented!");
|
||||||
|
return RawShape();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
static RawShape convexHull(const Shapes<RawShape>& /*sh*/)
|
||||||
|
{
|
||||||
|
static_assert(always_false<RawShape>::value,
|
||||||
|
"ShapeLike::convexHull(shapes) unimplemented!");
|
||||||
|
return RawShape();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
@ -437,13 +460,13 @@ struct ShapeLike {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
static TCountour<RawShape>& getHole(RawShape& sh, unsigned long idx)
|
static TContour<RawShape>& getHole(RawShape& sh, unsigned long idx)
|
||||||
{
|
{
|
||||||
return holes(sh)[idx];
|
return holes(sh)[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
static const TCountour<RawShape>& getHole(const RawShape& sh,
|
static const TContour<RawShape>& getHole(const RawShape& sh,
|
||||||
unsigned long idx)
|
unsigned long idx)
|
||||||
{
|
{
|
||||||
return holes(sh)[idx];
|
return holes(sh)[idx];
|
||||||
@ -456,13 +479,13 @@ struct ShapeLike {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
static TCountour<RawShape>& getContour(RawShape& sh)
|
static TContour<RawShape>& getContour(RawShape& sh)
|
||||||
{
|
{
|
||||||
return sh;
|
return sh;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
static const TCountour<RawShape>& getContour(const RawShape& sh)
|
static const TContour<RawShape>& getContour(const RawShape& sh)
|
||||||
{
|
{
|
||||||
return sh;
|
return sh;
|
||||||
}
|
}
|
||||||
@ -471,14 +494,14 @@ struct ShapeLike {
|
|||||||
static void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
|
static void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawShape>::value,
|
static_assert(always_false<RawShape>::value,
|
||||||
"ShapeLike::rotate() unimplemented");
|
"ShapeLike::rotate() unimplemented!");
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawShape, class RawPoint>
|
template<class RawShape, class RawPoint>
|
||||||
static void translate(RawShape& /*sh*/, const RawPoint& /*offs*/)
|
static void translate(RawShape& /*sh*/, const RawPoint& /*offs*/)
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawShape>::value,
|
static_assert(always_false<RawShape>::value,
|
||||||
"ShapeLike::translate() unimplemented");
|
"ShapeLike::translate() unimplemented!");
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
@ -490,12 +513,96 @@ struct ShapeLike {
|
|||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
static std::pair<bool, std::string> isValid(const RawShape& /*sh*/) {
|
static std::pair<bool, std::string> isValid(const RawShape& /*sh*/) {
|
||||||
return {false, "ShapeLike::isValid() unimplemented"};
|
return {false, "ShapeLike::isValid() unimplemented!"};
|
||||||
|
}
|
||||||
|
|
||||||
|
// *************************************************************************
|
||||||
|
// No need to implement these
|
||||||
|
// *************************************************************************
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
static inline _Box<TPoint<RawShape>> boundingBox(
|
||||||
|
const _Box<TPoint<RawShape>>& box)
|
||||||
|
{
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
static inline double area(const _Box<TPoint<RawShape>>& box)
|
||||||
|
{
|
||||||
|
return static_cast<double>(box.width() * box.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
static double area(const Shapes<RawShape>& shapes)
|
||||||
|
{
|
||||||
|
double ret = 0;
|
||||||
|
std::accumulate(shapes.first(), shapes.end(),
|
||||||
|
[](const RawShape& a, const RawShape& b) {
|
||||||
|
return area(a) + area(b);
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class RawShape> // Potential O(1) implementation may exist
|
||||||
|
static 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,
|
||||||
|
unsigned long idx)
|
||||||
|
{
|
||||||
|
return *(cbegin(sh) + idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
static 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) {
|
||||||
|
for(auto it = begin(sh); it != end(sh); ++it) fn(*it);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class RawShape, class Fn>
|
||||||
|
static 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class RawShape, class Fn>
|
||||||
|
static 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) {
|
||||||
|
for(int i = 0; i < holeCount(sh); ++i) {
|
||||||
|
auto& h = getHole(sh, i);
|
||||||
|
for(auto it = cbegin(h); it != cend(h); ++it) fn(*it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class RawShape, class Fn>
|
||||||
|
static 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) {
|
||||||
|
foreachContourVertex(sh, fn);
|
||||||
|
foreachHoleVertex(sh, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // GEOMETRY_TRAITS_HPP
|
#endif // GEOMETRY_TRAITS_HPP
|
||||||
|
@ -97,6 +97,12 @@ public:
|
|||||||
inline _Item(const std::initializer_list< Vertex >& il):
|
inline _Item(const std::initializer_list< Vertex >& il):
|
||||||
sh_(ShapeLike::create<RawShape>(il)) {}
|
sh_(ShapeLike::create<RawShape>(il)) {}
|
||||||
|
|
||||||
|
inline _Item(const TContour<RawShape>& contour):
|
||||||
|
sh_(ShapeLike::create<RawShape>(contour)) {}
|
||||||
|
|
||||||
|
inline _Item(TContour<RawShape>&& contour):
|
||||||
|
sh_(ShapeLike::create<RawShape>(std::move(contour))) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Convert the polygon to string representation. The format depends
|
* @brief Convert the polygon to string representation. The format depends
|
||||||
* on the implementation of the polygon.
|
* on the implementation of the polygon.
|
||||||
@ -174,7 +180,7 @@ public:
|
|||||||
double ret ;
|
double ret ;
|
||||||
if(area_cache_valid_) ret = area_cache_;
|
if(area_cache_valid_) ret = area_cache_;
|
||||||
else {
|
else {
|
||||||
ret = std::abs(ShapeLike::area(offsettedShape()));
|
ret = ShapeLike::area(offsettedShape());
|
||||||
area_cache_ = ret;
|
area_cache_ = ret;
|
||||||
area_cache_valid_ = true;
|
area_cache_valid_ = true;
|
||||||
}
|
}
|
||||||
@ -201,6 +207,8 @@ public:
|
|||||||
return ShapeLike::isInside(transformedShape(), sh.transformedShape());
|
return ShapeLike::isInside(transformedShape(), sh.transformedShape());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool isInside(const _Box<TPoint<RawShape>>& box);
|
||||||
|
|
||||||
inline void translate(const Vertex& d) BP2D_NOEXCEPT
|
inline void translate(const Vertex& d) BP2D_NOEXCEPT
|
||||||
{
|
{
|
||||||
translation_ += d; has_translation_ = true;
|
translation_ += d; has_translation_ = true;
|
||||||
@ -328,7 +336,7 @@ class _Rectangle: public _Item<RawShape> {
|
|||||||
using TO = Orientation;
|
using TO = Orientation;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
using Unit = TCoord<RawShape>;
|
using Unit = TCoord<TPoint<RawShape>>;
|
||||||
|
|
||||||
template<TO o = OrientationType<RawShape>::Value>
|
template<TO o = OrientationType<RawShape>::Value>
|
||||||
inline _Rectangle(Unit width, Unit height,
|
inline _Rectangle(Unit width, Unit height,
|
||||||
@ -367,6 +375,12 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
inline bool _Item<RawShape>::isInside(const _Box<TPoint<RawShape>>& box) {
|
||||||
|
_Rectangle<RawShape> rect(box.width(), box.height());
|
||||||
|
return _Item<RawShape>::isInside(rect);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief A wrapper interface (trait) class for any placement strategy provider.
|
* \brief A wrapper interface (trait) class for any placement strategy provider.
|
||||||
*
|
*
|
||||||
@ -481,8 +495,18 @@ public:
|
|||||||
/// Clear the packed items so a new session can be started.
|
/// Clear the packed items so a new session can be started.
|
||||||
inline void clearItems() { impl_.clearItems(); }
|
inline void clearItems() { impl_.clearItems(); }
|
||||||
|
|
||||||
|
#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
|
||||||
|
using ProgressFunction = std::function<void(unsigned)>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper interface (trait) class for any selections strategy provider.
|
* A wrapper interface (trait) class for any selections strategy provider.
|
||||||
*/
|
*/
|
||||||
@ -508,6 +532,14 @@ public:
|
|||||||
impl_.configure(config);
|
impl_.configure(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A function callback which should be called whenewer an item or
|
||||||
|
* a group of items where succesfully packed.
|
||||||
|
* @param fn A function callback object taking one unsigned integer as the
|
||||||
|
* number of the remaining items to pack.
|
||||||
|
*/
|
||||||
|
void progressIndicator(ProgressFunction fn) { impl_.progressIndicator(fn); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief A method to start the calculation on the input sequence.
|
* \brief A method to start the calculation on the input sequence.
|
||||||
*
|
*
|
||||||
@ -614,7 +646,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
BinType bin_;
|
BinType bin_;
|
||||||
PlacementConfig pconfig_;
|
PlacementConfig pconfig_;
|
||||||
TCoord<typename Item::ShapeType> min_obj_distance_;
|
Unit min_obj_distance_;
|
||||||
|
|
||||||
using SItem = typename SelectionStrategy::Item;
|
using SItem = typename SelectionStrategy::Item;
|
||||||
using TPItem = remove_cvref_t<Item>;
|
using TPItem = remove_cvref_t<Item>;
|
||||||
@ -680,6 +712,21 @@ public:
|
|||||||
return _arrange(from, to);
|
return _arrange(from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a progress indicatior function object for the selector.
|
||||||
|
inline void progressIndicator(ProgressFunction func)
|
||||||
|
{
|
||||||
|
selector_.progressIndicator(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline PackGroup lastResult() {
|
||||||
|
PackGroup ret;
|
||||||
|
for(size_t i = 0; i < selector_.binCount(); i++) {
|
||||||
|
auto items = selector_.itemsForBin(i);
|
||||||
|
ret.push_back(items);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
template<class TIterator,
|
template<class TIterator,
|
||||||
@ -694,14 +741,7 @@ private:
|
|||||||
inline PackGroup _arrange(TIterator from, TIterator to, bool = false)
|
inline PackGroup _arrange(TIterator from, TIterator to, bool = false)
|
||||||
{
|
{
|
||||||
__arrange(from, to);
|
__arrange(from, to);
|
||||||
|
return lastResult();
|
||||||
PackGroup ret;
|
|
||||||
for(size_t i = 0; i < selector_.binCount(); i++) {
|
|
||||||
auto items = selector_.itemsForBin(i);
|
|
||||||
ret.push_back(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class TIterator,
|
template<class TIterator,
|
||||||
@ -713,14 +753,7 @@ private:
|
|||||||
item_cache_ = {from, to};
|
item_cache_ = {from, to};
|
||||||
|
|
||||||
__arrange(item_cache_.begin(), item_cache_.end());
|
__arrange(item_cache_.begin(), item_cache_.end());
|
||||||
|
return lastResult();
|
||||||
PackGroup ret;
|
|
||||||
for(size_t i = 0; i < selector_.binCount(); i++) {
|
|
||||||
auto items = selector_.itemsForBin(i);
|
|
||||||
ret.push_back(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class TIterator,
|
template<class TIterator,
|
||||||
@ -784,7 +817,7 @@ private:
|
|||||||
template<class TIter> inline void __arrange(TIter from, TIter to)
|
template<class TIter> inline void __arrange(TIter from, TIter to)
|
||||||
{
|
{
|
||||||
if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) {
|
if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) {
|
||||||
item.addOffset(std::ceil(min_obj_distance_/2.0));
|
item.addOffset(static_cast<Unit>(std::ceil(min_obj_distance_/2.0)));
|
||||||
});
|
});
|
||||||
|
|
||||||
selector_.template packItems<PlacementStrategy>(
|
selector_.template packItems<PlacementStrategy>(
|
||||||
|
@ -2,29 +2,195 @@
|
|||||||
#define NOFITPOLY_HPP
|
#define NOFITPOLY_HPP
|
||||||
|
|
||||||
#include "placer_boilerplate.hpp"
|
#include "placer_boilerplate.hpp"
|
||||||
|
#include "../geometries_nfp.hpp"
|
||||||
|
|
||||||
namespace libnest2d { namespace strategies {
|
namespace libnest2d { namespace strategies {
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
struct NfpPConfig {
|
||||||
|
|
||||||
|
enum class Alignment {
|
||||||
|
CENTER,
|
||||||
|
BOTTOM_LEFT,
|
||||||
|
BOTTOM_RIGHT,
|
||||||
|
TOP_LEFT,
|
||||||
|
TOP_RIGHT,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool allow_rotations = false;
|
||||||
|
Alignment alignment;
|
||||||
|
};
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
|
class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
|
||||||
RawShape, _Box<TPoint<RawShape>>> {
|
RawShape, _Box<TPoint<RawShape>>, NfpPConfig<RawShape>> {
|
||||||
|
|
||||||
using Base = PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
|
using Base = PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
|
||||||
RawShape, _Box<TPoint<RawShape>>>;
|
RawShape, _Box<TPoint<RawShape>>, NfpPConfig<RawShape>>;
|
||||||
|
|
||||||
DECLARE_PLACER(Base)
|
DECLARE_PLACER(Base)
|
||||||
|
|
||||||
|
using Box = _Box<TPoint<RawShape>>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
inline explicit _NofitPolyPlacer(const BinType& bin): Base(bin) {}
|
inline explicit _NofitPolyPlacer(const BinType& bin): Base(bin) {}
|
||||||
|
|
||||||
PackResult trypack(Item& item) {
|
PackResult trypack(Item& item) {
|
||||||
|
|
||||||
return PackResult();
|
PackResult ret;
|
||||||
|
|
||||||
|
bool can_pack = false;
|
||||||
|
|
||||||
|
if(items_.empty()) {
|
||||||
|
setInitialPosition(item);
|
||||||
|
can_pack = item.isInside(bin_);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// place the new item outside of the print bed to make sure it is
|
||||||
|
// disjuct from the current merged pile
|
||||||
|
placeOutsideOfBin(item);
|
||||||
|
|
||||||
|
auto trsh = item.transformedShape();
|
||||||
|
Nfp::Shapes<RawShape> nfps;
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
#ifdef DEBUG_EXPORT_NFP
|
||||||
|
Base::debug_items_.clear();
|
||||||
|
#endif
|
||||||
|
auto v = ShapeLike::isValid(trsh);
|
||||||
|
assert(v.first);
|
||||||
|
#endif
|
||||||
|
for(Item& sh : items_) {
|
||||||
|
auto subnfp = Nfp::noFitPolygon(sh.transformedShape(),
|
||||||
|
trsh);
|
||||||
|
#ifndef NDEBUG
|
||||||
|
#ifdef DEBUG_EXPORT_NFP
|
||||||
|
Base::debug_items_.emplace_back(subnfp);
|
||||||
|
#endif
|
||||||
|
auto vv = ShapeLike::isValid(sh.transformedShape());
|
||||||
|
assert(vv.first);
|
||||||
|
|
||||||
|
auto vnfp = ShapeLike::isValid(subnfp);
|
||||||
|
assert(vnfp.first);
|
||||||
|
#endif
|
||||||
|
nfps = Nfp::merge(nfps, subnfp);
|
||||||
|
}
|
||||||
|
|
||||||
|
double min_area = std::numeric_limits<double>::max();
|
||||||
|
Vertex tr = {0, 0};
|
||||||
|
|
||||||
|
auto iv = Nfp::referenceVertex(trsh);
|
||||||
|
|
||||||
|
// place item on each the edge of this nfp
|
||||||
|
for(auto& nfp : nfps)
|
||||||
|
ShapeLike::foreachContourVertex(nfp, [&]
|
||||||
|
(Vertex& v)
|
||||||
|
{
|
||||||
|
Coord dx = getX(v) - getX(iv);
|
||||||
|
Coord dy = getY(v) - getY(iv);
|
||||||
|
|
||||||
|
Item placeditem(trsh);
|
||||||
|
placeditem.translate(Vertex(dx, dy));
|
||||||
|
|
||||||
|
if( placeditem.isInside(bin_) ) {
|
||||||
|
Nfp::Shapes<RawShape> m;
|
||||||
|
m.reserve(items_.size());
|
||||||
|
|
||||||
|
for(Item& pi : items_)
|
||||||
|
m.emplace_back(pi.transformedShape());
|
||||||
|
|
||||||
|
m.emplace_back(placeditem.transformedShape());
|
||||||
|
|
||||||
|
// auto b = ShapeLike::boundingBox(m);
|
||||||
|
|
||||||
|
// auto a = static_cast<double>(std::max(b.height(),
|
||||||
|
// b.width()));
|
||||||
|
|
||||||
|
auto b = ShapeLike::convexHull(m);
|
||||||
|
auto a = ShapeLike::area(b);
|
||||||
|
|
||||||
|
if(a < min_area) {
|
||||||
|
can_pack = true;
|
||||||
|
min_area = a;
|
||||||
|
tr = {dx, dy};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
for(auto&nfp : nfps) {
|
||||||
|
auto val = ShapeLike::isValid(nfp);
|
||||||
|
if(!val.first) std::cout << val.second << std::endl;
|
||||||
|
#ifdef DEBUG_EXPORT_NFP
|
||||||
|
Base::debug_items_.emplace_back(nfp);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
item.translate(tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(can_pack) {
|
||||||
|
ret = PackResult(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void setInitialPosition(Item& item) {
|
||||||
|
Box&& bb = item.boundingBox();
|
||||||
|
Vertex ci, cb;
|
||||||
|
|
||||||
|
switch(config_.alignment) {
|
||||||
|
case Config::Alignment::CENTER: {
|
||||||
|
ci = bb.center();
|
||||||
|
cb = bin_.center();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Config::Alignment::BOTTOM_LEFT: {
|
||||||
|
ci = bb.minCorner();
|
||||||
|
cb = bin_.minCorner();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Config::Alignment::BOTTOM_RIGHT: {
|
||||||
|
ci = {getX(bb.maxCorner()), getY(bb.minCorner())};
|
||||||
|
cb = {getX(bin_.maxCorner()), getY(bin_.minCorner())};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Config::Alignment::TOP_LEFT: {
|
||||||
|
ci = {getX(bb.minCorner()), getY(bb.maxCorner())};
|
||||||
|
cb = {getX(bin_.minCorner()), getY(bin_.maxCorner())};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Config::Alignment::TOP_RIGHT: {
|
||||||
|
ci = bb.maxCorner();
|
||||||
|
cb = bin_.maxCorner();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto d = cb - ci;
|
||||||
|
item.translate(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
void placeOutsideOfBin(Item& item) {
|
||||||
|
auto bb = item.boundingBox();
|
||||||
|
Box binbb = ShapeLike::boundingBox<RawShape>(bin_);
|
||||||
|
|
||||||
|
Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) };
|
||||||
|
|
||||||
|
Coord dx = getX(binbb.maxCorner()) - getX(v);
|
||||||
|
Coord dy = getY(binbb.maxCorner()) - getY(v);
|
||||||
|
|
||||||
|
item.translate({dx, dy});
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,16 +67,19 @@ public:
|
|||||||
|
|
||||||
void unpackLast() { items_.pop_back(); }
|
void unpackLast() { items_.pop_back(); }
|
||||||
|
|
||||||
inline ItemGroup getItems() { return items_; }
|
inline ItemGroup getItems() const { return items_; }
|
||||||
|
|
||||||
inline void clearItems() { items_.clear(); }
|
inline void clearItems() { items_.clear(); }
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
std::vector<Item> debug_items_;
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
BinType bin_;
|
BinType bin_;
|
||||||
Container items_;
|
Container items_;
|
||||||
Cfg config_;
|
Cfg config_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -84,9 +84,11 @@ public:
|
|||||||
bool try_reverse = config_.try_reverse_order;
|
bool try_reverse = config_.try_reverse_order;
|
||||||
|
|
||||||
// Will use a subroutine to add a new bin
|
// Will use a subroutine to add a new bin
|
||||||
auto addBin = [&placers, &free_area, &filled_area, &bin, &pconfig]()
|
auto addBin = [this, &placers, &free_area,
|
||||||
|
&filled_area, &bin, &pconfig]()
|
||||||
{
|
{
|
||||||
placers.emplace_back(bin);
|
placers.emplace_back(bin);
|
||||||
|
packed_bins_.emplace_back();
|
||||||
placers.back().configure(pconfig);
|
placers.back().configure(pconfig);
|
||||||
free_area = ShapeLike::area<RawShape>(bin);
|
free_area = ShapeLike::area<RawShape>(bin);
|
||||||
filled_area = 0;
|
filled_area = 0;
|
||||||
@ -457,6 +459,16 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto makeProgress = [this, ¬_packed](Placer& placer) {
|
||||||
|
packed_bins_.back() = placer.getItems();
|
||||||
|
#ifndef NDEBUG
|
||||||
|
packed_bins_.back().insert(packed_bins_.back().end(),
|
||||||
|
placer.getDebugItems().begin(),
|
||||||
|
placer.getDebugItems().end());
|
||||||
|
#endif
|
||||||
|
this->progress_(not_packed.size());
|
||||||
|
};
|
||||||
|
|
||||||
while(!not_packed.empty()) {
|
while(!not_packed.empty()) {
|
||||||
|
|
||||||
auto& placer = placers.back();
|
auto& placer = placers.back();
|
||||||
@ -472,30 +484,37 @@ public:
|
|||||||
free_area = bin_area - filled_area;
|
free_area = bin_area - filled_area;
|
||||||
auto itmp = it++;
|
auto itmp = it++;
|
||||||
not_packed.erase(itmp);
|
not_packed.erase(itmp);
|
||||||
|
makeProgress(placer);
|
||||||
} else it++;
|
} else it++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// try pieses one by one
|
// try pieses one by one
|
||||||
while(tryOneByOne(placer, waste))
|
while(tryOneByOne(placer, waste)) {
|
||||||
waste = 0;
|
waste = 0;
|
||||||
|
makeProgress(placer);
|
||||||
|
}
|
||||||
|
|
||||||
// try groups of 2 pieses
|
// try groups of 2 pieses
|
||||||
while(tryGroupsOfTwo(placer, waste))
|
while(tryGroupsOfTwo(placer, waste)) {
|
||||||
waste = 0;
|
waste = 0;
|
||||||
|
makeProgress(placer);
|
||||||
|
}
|
||||||
|
|
||||||
// try groups of 3 pieses
|
// try groups of 3 pieses
|
||||||
while(tryGroupsOfThree(placer, waste))
|
while(tryGroupsOfThree(placer, waste)) {
|
||||||
waste = 0;
|
waste = 0;
|
||||||
|
makeProgress(placer);
|
||||||
|
}
|
||||||
|
|
||||||
if(waste < free_area) waste += w;
|
if(waste < free_area) waste += w;
|
||||||
else if(!not_packed.empty()) addBin();
|
else if(!not_packed.empty()) addBin();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::for_each(placers.begin(), placers.end(),
|
// std::for_each(placers.begin(), placers.end(),
|
||||||
[this](Placer& placer){
|
// [this](Placer& placer){
|
||||||
packed_bins_.push_back(placer.getItems());
|
// packed_bins_.push_back(placer.getItems());
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,8 +26,14 @@ public:
|
|||||||
return packed_bins_[binIndex];
|
return packed_bins_[binIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void progressIndicator(ProgressFunction fn) {
|
||||||
|
progress_ = fn;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
PackGroup packed_bins_;
|
PackGroup packed_bins_;
|
||||||
|
ProgressFunction progress_ = [](unsigned){};
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
|
||||||
# Try to find existing GTest installation
|
# Try to find existing GTest installation
|
||||||
find_package(GTest QUIET)
|
find_package(GTest 1.7)
|
||||||
|
|
||||||
if(NOT GTEST_FOUND)
|
if(NOT GTEST_FOUND)
|
||||||
|
message(STATUS "GTest not found so downloading...")
|
||||||
# Go and download google test framework, integrate it with the build
|
# Go and download google test framework, integrate it with the build
|
||||||
set(GTEST_LIBRARIES gtest gmock)
|
set(GTEST_LIBS_TO_LINK gtest gtest_main)
|
||||||
|
|
||||||
if (CMAKE_VERSION VERSION_LESS 3.2)
|
if (CMAKE_VERSION VERSION_LESS 3.2)
|
||||||
set(UPDATE_DISCONNECTED_IF_AVAILABLE "")
|
set(UPDATE_DISCONNECTED_IF_AVAILABLE "")
|
||||||
@ -15,7 +16,7 @@ if(NOT GTEST_FOUND)
|
|||||||
include(DownloadProject)
|
include(DownloadProject)
|
||||||
download_project(PROJ googletest
|
download_project(PROJ googletest
|
||||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||||
GIT_TAG release-1.8.0
|
GIT_TAG release-1.7.0
|
||||||
${UPDATE_DISCONNECTED_IF_AVAILABLE}
|
${UPDATE_DISCONNECTED_IF_AVAILABLE}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,22 +28,20 @@ if(NOT GTEST_FOUND)
|
|||||||
${googletest_BINARY_DIR}
|
${googletest_BINARY_DIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(GTEST_INCLUDE_DIRS ${googletest_SOURCE_DIR}/include)
|
||||||
|
|
||||||
else()
|
else()
|
||||||
include_directories(${GTEST_INCLUDE_DIRS} )
|
find_package(Threads REQUIRED)
|
||||||
|
set(GTEST_LIBS_TO_LINK ${GTEST_BOTH_LIBRARIES} Threads::Threads)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include_directories(BEFORE ${LIBNEST2D_HEADERS})
|
add_executable(bp2d_tests test.cpp svgtools.hpp printer_parts.h printer_parts.cpp)
|
||||||
add_executable(bp2d_tests test.cpp printer_parts.h printer_parts.cpp)
|
target_link_libraries(bp2d_tests libnest2d_static ${GTEST_LIBS_TO_LINK} )
|
||||||
target_link_libraries(bp2d_tests libnest2d
|
target_include_directories(bp2d_tests PRIVATE BEFORE ${LIBNEST2D_HEADERS}
|
||||||
${GTEST_LIBRARIES}
|
${GTEST_INCLUDE_DIRS})
|
||||||
)
|
|
||||||
|
|
||||||
if(DEFINED LIBNEST2D_TEST_LIBRARIES)
|
if(DEFINED LIBNEST2D_TEST_LIBRARIES)
|
||||||
target_link_libraries(bp2d_tests ${LIBNEST2D_TEST_LIBRARIES})
|
target_link_libraries(bp2d_tests ${LIBNEST2D_TEST_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_test(gtests bp2d_tests)
|
add_test(libnest2d_tests bp2d_tests)
|
||||||
|
|
||||||
add_executable(main EXCLUDE_FROM_ALL main.cpp printer_parts.cpp printer_parts.h)
|
|
||||||
target_link_libraries(main libnest2d)
|
|
||||||
target_include_directories(main PUBLIC ${CMAKE_SOURCE_DIR})
|
|
||||||
|
@ -7,232 +7,56 @@
|
|||||||
|
|
||||||
#include "printer_parts.h"
|
#include "printer_parts.h"
|
||||||
#include "benchmark.h"
|
#include "benchmark.h"
|
||||||
|
#include "svgtools.hpp"
|
||||||
|
|
||||||
namespace {
|
|
||||||
using namespace libnest2d;
|
using namespace libnest2d;
|
||||||
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
|
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
|
||||||
//using PackGroup = std::vector<ItemGroup>;
|
|
||||||
|
|
||||||
template<int SCALE, class Bin >
|
std::vector<Item>& _parts(std::vector<Item>& ret, const TestData& data)
|
||||||
void exportSVG(PackGroup& result, const Bin& bin) {
|
{
|
||||||
|
if(ret.empty()) {
|
||||||
std::string loc = "out";
|
ret.reserve(data.size());
|
||||||
|
for(auto& inp : data)
|
||||||
static std::string svg_header =
|
ret.emplace_back(inp);
|
||||||
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
||||||
<svg height="500" width="500" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
)raw";
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for(auto r : result) {
|
|
||||||
std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out);
|
|
||||||
if(out.is_open()) {
|
|
||||||
out << svg_header;
|
|
||||||
Item rbin( Rectangle(bin.width(), bin.height()) );
|
|
||||||
for(unsigned i = 0; i < rbin.vertexCount(); i++) {
|
|
||||||
auto v = rbin.vertex(i);
|
|
||||||
setY(v, -getY(v)/SCALE + 500 );
|
|
||||||
setX(v, getX(v)/SCALE);
|
|
||||||
rbin.setVertex(i, v);
|
|
||||||
}
|
|
||||||
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++) {
|
|
||||||
auto v = tsh.vertex(i);
|
|
||||||
setY(v, -getY(v)/SCALE + 500);
|
|
||||||
setX(v, getX(v)/SCALE);
|
|
||||||
tsh.setVertex(i, v);
|
|
||||||
}
|
|
||||||
out << ShapeLike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
|
|
||||||
}
|
|
||||||
out << "\n</svg>" << std::endl;
|
|
||||||
}
|
|
||||||
out.close();
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
template< int SCALE, class Bin>
|
std::vector<Item>& prusaParts() {
|
||||||
void exportSVG(ItemGroup& result, const Bin& bin, int idx) {
|
static std::vector<Item> ret;
|
||||||
|
return _parts(ret, PRINTER_PART_POLYGONS);
|
||||||
std::string loc = "out";
|
|
||||||
|
|
||||||
static std::string svg_header =
|
|
||||||
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
||||||
<svg height="500" width="500" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
)raw";
|
|
||||||
|
|
||||||
int i = idx;
|
|
||||||
auto r = result;
|
|
||||||
// for(auto r : result) {
|
|
||||||
std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out);
|
|
||||||
if(out.is_open()) {
|
|
||||||
out << svg_header;
|
|
||||||
Item rbin( Rectangle(bin.width(), bin.height()) );
|
|
||||||
for(unsigned i = 0; i < rbin.vertexCount(); i++) {
|
|
||||||
auto v = rbin.vertex(i);
|
|
||||||
setY(v, -getY(v)/SCALE + 500 );
|
|
||||||
setX(v, getX(v)/SCALE);
|
|
||||||
rbin.setVertex(i, v);
|
|
||||||
}
|
|
||||||
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++) {
|
|
||||||
auto v = tsh.vertex(i);
|
|
||||||
setY(v, -getY(v)/SCALE + 500);
|
|
||||||
setX(v, getX(v)/SCALE);
|
|
||||||
tsh.setVertex(i, v);
|
|
||||||
}
|
|
||||||
out << ShapeLike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
|
|
||||||
}
|
|
||||||
out << "\n</svg>" << std::endl;
|
|
||||||
}
|
|
||||||
out.close();
|
|
||||||
|
|
||||||
// i++;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<Item>& stegoParts() {
|
||||||
void findDegenerateCase() {
|
static std::vector<Item> ret;
|
||||||
using namespace libnest2d;
|
return _parts(ret, STEGOSAUR_POLYGONS);
|
||||||
|
|
||||||
auto input = PRINTER_PART_POLYGONS;
|
|
||||||
|
|
||||||
auto scaler = [](Item& item) {
|
|
||||||
for(unsigned i = 0; i < item.vertexCount(); i++) {
|
|
||||||
auto v = item.vertex(i);
|
|
||||||
setX(v, 100*getX(v)); setY(v, 100*getY(v));
|
|
||||||
item.setVertex(i, v);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto cmp = [](const Item& t1, const Item& t2) {
|
|
||||||
return t1.area() > t2.area();
|
|
||||||
};
|
|
||||||
|
|
||||||
std::for_each(input.begin(), input.end(), scaler);
|
|
||||||
|
|
||||||
std::sort(input.begin(), input.end(), cmp);
|
|
||||||
|
|
||||||
Box bin(210*100, 250*100);
|
|
||||||
BottomLeftPlacer placer(bin);
|
|
||||||
|
|
||||||
auto it = input.begin();
|
|
||||||
auto next = it;
|
|
||||||
int i = 0;
|
|
||||||
while(it != input.end() && ++next != input.end()) {
|
|
||||||
placer.pack(*it);
|
|
||||||
placer.pack(*next);
|
|
||||||
|
|
||||||
auto result = placer.getItems();
|
|
||||||
bool valid = true;
|
|
||||||
|
|
||||||
if(result.size() == 2) {
|
|
||||||
Item& r1 = result[0];
|
|
||||||
Item& r2 = result[1];
|
|
||||||
valid = !Item::intersects(r1, r2) || Item::touches(r1, r2);
|
|
||||||
valid = (valid && !r1.isInside(r2) && !r2.isInside(r1));
|
|
||||||
if(!valid) {
|
|
||||||
std::cout << "error index: " << i << std::endl;
|
|
||||||
exportSVG<100>(result, bin, i);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
std::cout << "something went terribly wrong!" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
placer.clearItems();
|
|
||||||
it++;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void arrangeRectangles() {
|
void arrangeRectangles() {
|
||||||
using namespace libnest2d;
|
using namespace libnest2d;
|
||||||
|
|
||||||
|
auto input = stegoParts();
|
||||||
// std::vector<Rectangle> input = {
|
|
||||||
// {80, 80},
|
|
||||||
// {110, 10},
|
|
||||||
// {200, 5},
|
|
||||||
// {80, 30},
|
|
||||||
// {60, 90},
|
|
||||||
// {70, 30},
|
|
||||||
// {80, 60},
|
|
||||||
// {60, 60},
|
|
||||||
// {60, 40},
|
|
||||||
// {40, 40},
|
|
||||||
// {10, 10},
|
|
||||||
// {10, 10},
|
|
||||||
// {10, 10},
|
|
||||||
// {10, 10},
|
|
||||||
// {10, 10},
|
|
||||||
// {5, 5},
|
|
||||||
// {5, 5},
|
|
||||||
// {5, 5},
|
|
||||||
// {5, 5},
|
|
||||||
// {5, 5},
|
|
||||||
// {5, 5},
|
|
||||||
// {5, 5},
|
|
||||||
// {20, 20},
|
|
||||||
// {80, 80},
|
|
||||||
// {110, 10},
|
|
||||||
// {200, 5},
|
|
||||||
// {80, 30},
|
|
||||||
// {60, 90},
|
|
||||||
// {70, 30},
|
|
||||||
// {80, 60},
|
|
||||||
// {60, 60},
|
|
||||||
// {60, 40},
|
|
||||||
// {40, 40},
|
|
||||||
// {10, 10},
|
|
||||||
// {10, 10},
|
|
||||||
// {10, 10},
|
|
||||||
// {10, 10},
|
|
||||||
// {10, 10},
|
|
||||||
// {5, 5},
|
|
||||||
// {5, 5},
|
|
||||||
// {5, 5},
|
|
||||||
// {5, 5},
|
|
||||||
// {5, 5},
|
|
||||||
// {5, 5},
|
|
||||||
// {5, 5},
|
|
||||||
// {20, 20}
|
|
||||||
// };
|
|
||||||
|
|
||||||
auto input = PRINTER_PART_POLYGONS;
|
|
||||||
|
|
||||||
const int SCALE = 1000000;
|
const int SCALE = 1000000;
|
||||||
// const int SCALE = 1;
|
|
||||||
|
|
||||||
Box bin(210*SCALE, 250*SCALE);
|
Box bin(210*SCALE, 250*SCALE);
|
||||||
|
|
||||||
auto scaler = [&SCALE, &bin](Item& item) {
|
Coord min_obj_distance = 0; //6*SCALE;
|
||||||
// double max_area = 0;
|
|
||||||
for(unsigned i = 0; i < item.vertexCount(); i++) {
|
|
||||||
auto v = item.vertex(i);
|
|
||||||
setX(v, SCALE*getX(v)); setY(v, SCALE*getY(v));
|
|
||||||
item.setVertex(i, v);
|
|
||||||
// double area = item.area();
|
|
||||||
// if(max_area < area) {
|
|
||||||
// max_area = area;
|
|
||||||
// bin = item.boundingBox();
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Coord min_obj_distance = 2*SCALE;
|
NfpPlacer::Config pconf;
|
||||||
|
pconf.alignment = NfpPlacer::Config::Alignment::TOP_LEFT;
|
||||||
|
Arranger<NfpPlacer, DJDHeuristic> arrange(bin, min_obj_distance, pconf);
|
||||||
|
|
||||||
std::for_each(input.begin(), input.end(), scaler);
|
// arrange.progressIndicator([&arrange, &bin](unsigned r){
|
||||||
|
// svg::SVGWriter::Config conf;
|
||||||
Arranger<BottomLeftPlacer, DJDHeuristic> arrange(bin, min_obj_distance);
|
// conf.mm_in_coord_units = SCALE;
|
||||||
|
// svg::SVGWriter svgw(conf);
|
||||||
|
// svgw.setSize(bin);
|
||||||
|
// svgw.writePackGroup(arrange.lastResult());
|
||||||
|
// svgw.save("out");
|
||||||
|
// std::cout << "Remaining items: " << r << std::endl;
|
||||||
|
// });
|
||||||
|
|
||||||
Benchmark bench;
|
Benchmark bench;
|
||||||
|
|
||||||
@ -249,8 +73,12 @@ void arrangeRectangles() {
|
|||||||
std::cout << ret.second << std::endl;
|
std::cout << ret.second << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
exportSVG<SCALE>(result, bin);
|
svg::SVGWriter::Config conf;
|
||||||
|
conf.mm_in_coord_units = SCALE;
|
||||||
|
svg::SVGWriter svgw(conf);
|
||||||
|
svgw.setSize(bin);
|
||||||
|
svgw.writePackGroup(result);
|
||||||
|
svgw.save("out");
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void /*int argc, char **argv*/) {
|
int main(void /*int argc, char **argv*/) {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -2,8 +2,11 @@
|
|||||||
#define PRINTER_PARTS_H
|
#define PRINTER_PARTS_H
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <libnest2d.h>
|
#include <clipper.hpp>
|
||||||
|
|
||||||
extern const std::vector<libnest2d::Item> PRINTER_PART_POLYGONS;
|
using TestData = std::vector<ClipperLib::Path>;
|
||||||
|
|
||||||
|
extern const TestData PRINTER_PART_POLYGONS;
|
||||||
|
extern const TestData STEGOSAUR_POLYGONS;
|
||||||
|
|
||||||
#endif // PRINTER_PARTS_H
|
#endif // PRINTER_PARTS_H
|
||||||
|
112
xs/src/libnest2d/tests/svgtools.hpp
Normal file
112
xs/src/libnest2d/tests/svgtools.hpp
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
#ifndef SVGTOOLS_HPP
|
||||||
|
#define SVGTOOLS_HPP
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <libnest2d.h>
|
||||||
|
#include <libnest2d/geometries_io.hpp>
|
||||||
|
|
||||||
|
namespace libnest2d { namespace svg {
|
||||||
|
|
||||||
|
class SVGWriter {
|
||||||
|
public:
|
||||||
|
|
||||||
|
enum OrigoLocation {
|
||||||
|
TOPLEFT,
|
||||||
|
BOTTOMLEFT
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
OrigoLocation origo_location;
|
||||||
|
Coord mm_in_coord_units;
|
||||||
|
double width, height;
|
||||||
|
Config():
|
||||||
|
origo_location(BOTTOMLEFT), mm_in_coord_units(1000000),
|
||||||
|
width(500), height(500) {}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
Config conf_;
|
||||||
|
std::vector<std::string> svg_layers_;
|
||||||
|
bool finished_ = false;
|
||||||
|
public:
|
||||||
|
|
||||||
|
SVGWriter(const Config& conf = Config()):
|
||||||
|
conf_(conf) {}
|
||||||
|
|
||||||
|
void setSize(const Box& box) {
|
||||||
|
conf_.height = static_cast<double>(box.height()) /
|
||||||
|
conf_.mm_in_coord_units;
|
||||||
|
conf_.width = static_cast<double>(box.width()) /
|
||||||
|
conf_.mm_in_coord_units;
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeItem(const Item& item) {
|
||||||
|
if(svg_layers_.empty()) addLayer();
|
||||||
|
Item tsh(item.transformedShape());
|
||||||
|
if(conf_.origo_location == BOTTOMLEFT)
|
||||||
|
for(unsigned i = 0; i < tsh.vertexCount(); i++) {
|
||||||
|
auto v = tsh.vertex(i);
|
||||||
|
setY(v, -getY(v) + conf_.height*conf_.mm_in_coord_units);
|
||||||
|
tsh.setVertex(i, v);
|
||||||
|
}
|
||||||
|
currentLayer() += ShapeLike::serialize<Formats::SVG>(tsh.rawShape(),
|
||||||
|
1.0/conf_.mm_in_coord_units) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void writePackGroup(const PackGroup& result) {
|
||||||
|
for(auto r : result) {
|
||||||
|
addLayer();
|
||||||
|
for(Item& sh : r) {
|
||||||
|
writeItem(sh);
|
||||||
|
}
|
||||||
|
finishLayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addLayer() {
|
||||||
|
svg_layers_.emplace_back(header());
|
||||||
|
finished_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finishLayer() {
|
||||||
|
currentLayer() += "\n</svg>\n";
|
||||||
|
finished_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void save(const std::string& filepath) {
|
||||||
|
unsigned lyrc = svg_layers_.size() > 1? 1 : 0;
|
||||||
|
unsigned last = svg_layers_.size() > 1? svg_layers_.size() : 0;
|
||||||
|
|
||||||
|
for(auto& lyr : svg_layers_) {
|
||||||
|
std::fstream out(filepath + (lyrc > 0? std::to_string(lyrc) : "") +
|
||||||
|
".svg", std::fstream::out);
|
||||||
|
if(out.is_open()) out << lyr;
|
||||||
|
if(lyrc == last && !finished_) out << "\n</svg>\n";
|
||||||
|
out.flush(); out.close(); lyrc++;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::string& currentLayer() { return svg_layers_.back(); }
|
||||||
|
|
||||||
|
const std::string header() const {
|
||||||
|
std::string svg_header =
|
||||||
|
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg height=")raw";
|
||||||
|
svg_header += std::to_string(conf_.height) + "\" width=\"" + std::to_string(conf_.width) + "\" ";
|
||||||
|
svg_header += R"raw(xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">)raw";
|
||||||
|
return svg_header;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SVGTOOLS_HPP
|
@ -1,5 +1,4 @@
|
|||||||
#include "gtest/gtest.h"
|
#include <gtest/gtest.h>
|
||||||
#include "gmock/gmock.h"
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#include <libnest2d.h>
|
#include <libnest2d.h>
|
||||||
@ -7,6 +6,17 @@
|
|||||||
#include <libnest2d/geometries_io.hpp>
|
#include <libnest2d/geometries_io.hpp>
|
||||||
#include <libnest2d/geometries_nfp.hpp>
|
#include <libnest2d/geometries_nfp.hpp>
|
||||||
|
|
||||||
|
std::vector<libnest2d::Item>& prusaParts() {
|
||||||
|
static std::vector<libnest2d::Item> ret;
|
||||||
|
|
||||||
|
if(ret.empty()) {
|
||||||
|
ret.reserve(PRINTER_PART_POLYGONS.size());
|
||||||
|
for(auto& inp : PRINTER_PART_POLYGONS) ret.emplace_back(inp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
TEST(BasicFunctionality, Angles)
|
TEST(BasicFunctionality, Angles)
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -24,6 +34,44 @@ TEST(BasicFunctionality, Angles)
|
|||||||
|
|
||||||
ASSERT_TRUE(rad == deg);
|
ASSERT_TRUE(rad == deg);
|
||||||
|
|
||||||
|
Segment seg = {{0, 0}, {12, -10}};
|
||||||
|
|
||||||
|
ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 270 &&
|
||||||
|
Degrees(seg.angleToXaxis()) < 360);
|
||||||
|
|
||||||
|
seg = {{0, 0}, {12, 10}};
|
||||||
|
|
||||||
|
ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 0 &&
|
||||||
|
Degrees(seg.angleToXaxis()) < 90);
|
||||||
|
|
||||||
|
seg = {{0, 0}, {-12, 10}};
|
||||||
|
|
||||||
|
ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 90 &&
|
||||||
|
Degrees(seg.angleToXaxis()) < 180);
|
||||||
|
|
||||||
|
seg = {{0, 0}, {-12, -10}};
|
||||||
|
|
||||||
|
ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 180 &&
|
||||||
|
Degrees(seg.angleToXaxis()) < 270);
|
||||||
|
|
||||||
|
seg = {{0, 0}, {1, 0}};
|
||||||
|
|
||||||
|
ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 0);
|
||||||
|
|
||||||
|
seg = {{0, 0}, {0, 1}};
|
||||||
|
|
||||||
|
ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 90);
|
||||||
|
|
||||||
|
|
||||||
|
seg = {{0, 0}, {-1, 0}};
|
||||||
|
|
||||||
|
ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 180);
|
||||||
|
|
||||||
|
|
||||||
|
seg = {{0, 0}, {0, -1}};
|
||||||
|
|
||||||
|
ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 270);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple test, does not use gmock
|
// Simple test, does not use gmock
|
||||||
@ -33,21 +81,21 @@ TEST(BasicFunctionality, creationAndDestruction)
|
|||||||
|
|
||||||
Item sh = { {0, 0}, {1, 0}, {1, 1}, {0, 1} };
|
Item sh = { {0, 0}, {1, 0}, {1, 1}, {0, 1} };
|
||||||
|
|
||||||
ASSERT_EQ(sh.vertexCount(), 4);
|
ASSERT_EQ(sh.vertexCount(), 4u);
|
||||||
|
|
||||||
Item sh2 ({ {0, 0}, {1, 0}, {1, 1}, {0, 1} });
|
Item sh2 ({ {0, 0}, {1, 0}, {1, 1}, {0, 1} });
|
||||||
|
|
||||||
ASSERT_EQ(sh2.vertexCount(), 4);
|
ASSERT_EQ(sh2.vertexCount(), 4u);
|
||||||
|
|
||||||
// copy
|
// copy
|
||||||
Item sh3 = sh2;
|
Item sh3 = sh2;
|
||||||
|
|
||||||
ASSERT_EQ(sh3.vertexCount(), 4);
|
ASSERT_EQ(sh3.vertexCount(), 4u);
|
||||||
|
|
||||||
sh2 = {};
|
sh2 = {};
|
||||||
|
|
||||||
ASSERT_EQ(sh2.vertexCount(), 0);
|
ASSERT_EQ(sh2.vertexCount(), 0u);
|
||||||
ASSERT_EQ(sh3.vertexCount(), 4);
|
ASSERT_EQ(sh3.vertexCount(), 4u);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +118,8 @@ TEST(GeometryAlgorithms, Distance) {
|
|||||||
|
|
||||||
auto check = [](Coord val, Coord expected) {
|
auto check = [](Coord val, Coord expected) {
|
||||||
if(std::is_floating_point<Coord>::value)
|
if(std::is_floating_point<Coord>::value)
|
||||||
ASSERT_DOUBLE_EQ(static_cast<double>(val), expected);
|
ASSERT_DOUBLE_EQ(static_cast<double>(val),
|
||||||
|
static_cast<double>(expected));
|
||||||
else
|
else
|
||||||
ASSERT_EQ(val, expected);
|
ASSERT_EQ(val, expected);
|
||||||
};
|
};
|
||||||
@ -112,6 +161,18 @@ TEST(GeometryAlgorithms, Area) {
|
|||||||
|
|
||||||
ASSERT_EQ(rect2.area(), 10000);
|
ASSERT_EQ(rect2.area(), 10000);
|
||||||
|
|
||||||
|
Item item = {
|
||||||
|
{61, 97},
|
||||||
|
{70, 151},
|
||||||
|
{176, 151},
|
||||||
|
{189, 138},
|
||||||
|
{189, 59},
|
||||||
|
{70, 59},
|
||||||
|
{61, 77},
|
||||||
|
{61, 97}
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT_TRUE(ShapeLike::area(item.transformedShape()) > 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GeometryAlgorithms, IsPointInsidePolygon) {
|
TEST(GeometryAlgorithms, IsPointInsidePolygon) {
|
||||||
@ -240,7 +301,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight)
|
|||||||
|
|
||||||
auto groups = arrange(rects.begin(), rects.end());
|
auto groups = arrange(rects.begin(), rects.end());
|
||||||
|
|
||||||
ASSERT_EQ(groups.size(), 1);
|
ASSERT_EQ(groups.size(), 1u);
|
||||||
ASSERT_EQ(groups[0].size(), rects.size());
|
ASSERT_EQ(groups[0].size(), rects.size());
|
||||||
|
|
||||||
// check for no intersections, no containment:
|
// check for no intersections, no containment:
|
||||||
@ -294,7 +355,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose)
|
|||||||
|
|
||||||
auto groups = arrange(rects.begin(), rects.end());
|
auto groups = arrange(rects.begin(), rects.end());
|
||||||
|
|
||||||
ASSERT_EQ(groups.size(), 1);
|
ASSERT_EQ(groups.size(), 1u);
|
||||||
ASSERT_EQ(groups[0].size(), rects.size());
|
ASSERT_EQ(groups[0].size(), rects.size());
|
||||||
|
|
||||||
// check for no intersections, no containment:
|
// check for no intersections, no containment:
|
||||||
@ -363,7 +424,7 @@ R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|||||||
TEST(GeometryAlgorithms, BottomLeftStressTest) {
|
TEST(GeometryAlgorithms, BottomLeftStressTest) {
|
||||||
using namespace libnest2d;
|
using namespace libnest2d;
|
||||||
|
|
||||||
auto input = PRINTER_PART_POLYGONS;
|
auto& input = prusaParts();
|
||||||
|
|
||||||
Box bin(210, 250);
|
Box bin(210, 250);
|
||||||
BottomLeftPlacer placer(bin);
|
BottomLeftPlacer placer(bin);
|
||||||
@ -399,73 +460,240 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct ItemPair {
|
||||||
|
Item orbiter;
|
||||||
|
Item stationary;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<ItemPair> nfp_testdata = {
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{80, 50},
|
||||||
|
{100, 70},
|
||||||
|
{120, 50},
|
||||||
|
{80, 50}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{10, 10},
|
||||||
|
{10, 40},
|
||||||
|
{40, 40},
|
||||||
|
{40, 10},
|
||||||
|
{10, 10}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{80, 50},
|
||||||
|
{60, 70},
|
||||||
|
{80, 90},
|
||||||
|
{120, 90},
|
||||||
|
{140, 70},
|
||||||
|
{120, 50},
|
||||||
|
{80, 50}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{10, 10},
|
||||||
|
{10, 40},
|
||||||
|
{40, 40},
|
||||||
|
{40, 10},
|
||||||
|
{10, 10}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{40, 10},
|
||||||
|
{30, 10},
|
||||||
|
{20, 20},
|
||||||
|
{20, 30},
|
||||||
|
{30, 40},
|
||||||
|
{40, 40},
|
||||||
|
{50, 30},
|
||||||
|
{50, 20},
|
||||||
|
{40, 10}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{80, 0},
|
||||||
|
{80, 30},
|
||||||
|
{110, 30},
|
||||||
|
{110, 0},
|
||||||
|
{80, 0}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{117, 107},
|
||||||
|
{118, 109},
|
||||||
|
{120, 112},
|
||||||
|
{122, 113},
|
||||||
|
{128, 113},
|
||||||
|
{130, 112},
|
||||||
|
{132, 109},
|
||||||
|
{133, 107},
|
||||||
|
{133, 103},
|
||||||
|
{132, 101},
|
||||||
|
{130, 98},
|
||||||
|
{128, 97},
|
||||||
|
{122, 97},
|
||||||
|
{120, 98},
|
||||||
|
{118, 101},
|
||||||
|
{117, 103},
|
||||||
|
{117, 107}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{102, 116},
|
||||||
|
{111, 126},
|
||||||
|
{114, 126},
|
||||||
|
{144, 106},
|
||||||
|
{148, 100},
|
||||||
|
{148, 85},
|
||||||
|
{147, 84},
|
||||||
|
{102, 84},
|
||||||
|
{102, 116},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{99, 122},
|
||||||
|
{108, 140},
|
||||||
|
{110, 142},
|
||||||
|
{139, 142},
|
||||||
|
{151, 122},
|
||||||
|
{151, 102},
|
||||||
|
{142, 70},
|
||||||
|
{139, 68},
|
||||||
|
{111, 68},
|
||||||
|
{108, 70},
|
||||||
|
{99, 102},
|
||||||
|
{99, 122},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{107, 124},
|
||||||
|
{128, 125},
|
||||||
|
{133, 125},
|
||||||
|
{136, 124},
|
||||||
|
{140, 121},
|
||||||
|
{142, 119},
|
||||||
|
{143, 116},
|
||||||
|
{143, 109},
|
||||||
|
{141, 93},
|
||||||
|
{139, 89},
|
||||||
|
{136, 86},
|
||||||
|
{134, 85},
|
||||||
|
{108, 85},
|
||||||
|
{107, 86},
|
||||||
|
{107, 124},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{91, 100},
|
||||||
|
{94, 144},
|
||||||
|
{117, 153},
|
||||||
|
{118, 153},
|
||||||
|
{159, 112},
|
||||||
|
{159, 110},
|
||||||
|
{156, 66},
|
||||||
|
{133, 57},
|
||||||
|
{132, 57},
|
||||||
|
{91, 98},
|
||||||
|
{91, 100},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{101, 90},
|
||||||
|
{103, 98},
|
||||||
|
{107, 113},
|
||||||
|
{114, 125},
|
||||||
|
{115, 126},
|
||||||
|
{135, 126},
|
||||||
|
{136, 125},
|
||||||
|
{144, 114},
|
||||||
|
{149, 90},
|
||||||
|
{149, 89},
|
||||||
|
{148, 87},
|
||||||
|
{145, 84},
|
||||||
|
{105, 84},
|
||||||
|
{102, 87},
|
||||||
|
{101, 89},
|
||||||
|
{101, 90},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
TEST(GeometryAlgorithms, nfpConvexConvex) {
|
TEST(GeometryAlgorithms, nfpConvexConvex) {
|
||||||
using namespace libnest2d;
|
using namespace libnest2d;
|
||||||
|
|
||||||
const unsigned long SCALE = 1;
|
const Coord SCALE = 1000000;
|
||||||
|
|
||||||
Box bin(210*SCALE, 250*SCALE);
|
Box bin(210*SCALE, 250*SCALE);
|
||||||
|
|
||||||
Item stationary = {
|
int testcase = 0;
|
||||||
{120, 114},
|
|
||||||
{130, 114},
|
|
||||||
{130, 103},
|
|
||||||
{128, 96},
|
|
||||||
{122, 96},
|
|
||||||
{120, 103},
|
|
||||||
{120, 114}
|
|
||||||
};
|
|
||||||
|
|
||||||
Item orbiter = {
|
auto& exportfun = exportSVG<1, Box>;
|
||||||
{72, 147},
|
|
||||||
{94, 151},
|
|
||||||
{178, 151},
|
|
||||||
{178, 59},
|
|
||||||
{72, 59},
|
|
||||||
{72, 147}
|
|
||||||
};
|
|
||||||
|
|
||||||
orbiter.translate({210*SCALE, 0});
|
auto onetest = [&](Item& orbiter, Item& stationary){
|
||||||
|
testcase++;
|
||||||
|
|
||||||
auto&& nfp = Nfp::noFitPolygon(stationary.rawShape(),
|
orbiter.translate({210*SCALE, 0});
|
||||||
orbiter.transformedShape());
|
|
||||||
|
|
||||||
auto v = ShapeLike::isValid(nfp);
|
auto&& nfp = Nfp::noFitPolygon(stationary.rawShape(),
|
||||||
|
orbiter.transformedShape());
|
||||||
|
|
||||||
if(!v.first) {
|
auto v = ShapeLike::isValid(nfp);
|
||||||
std::cout << v.second << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT_TRUE(v.first);
|
if(!v.first) {
|
||||||
|
std::cout << v.second << std::endl;
|
||||||
Item infp(nfp);
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
auto rorbiter = orbiter.transformedShape();
|
|
||||||
auto vo = *(ShapeLike::begin(rorbiter));
|
|
||||||
for(auto v : infp) {
|
|
||||||
auto dx = getX(v) - getX(vo);
|
|
||||||
auto dy = getY(v) - getY(vo);
|
|
||||||
|
|
||||||
Item tmp = orbiter;
|
|
||||||
|
|
||||||
tmp.translate({dx, dy});
|
|
||||||
|
|
||||||
bool notinside = !tmp.isInside(stationary);
|
|
||||||
bool notintersecting = !Item::intersects(tmp, stationary);
|
|
||||||
|
|
||||||
if(!(notinside && notintersecting)) {
|
|
||||||
std::vector<std::reference_wrapper<Item>> inp = {
|
|
||||||
std::ref(stationary), std::ref(tmp), std::ref(infp)
|
|
||||||
};
|
|
||||||
|
|
||||||
exportSVG<SCALE>(inp, bin, i++);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//ASSERT_TRUE(notintersecting);
|
ASSERT_TRUE(v.first);
|
||||||
ASSERT_TRUE(notinside);
|
|
||||||
|
Item infp(nfp);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
auto rorbiter = orbiter.transformedShape();
|
||||||
|
auto vo = Nfp::referenceVertex(rorbiter);
|
||||||
|
|
||||||
|
ASSERT_TRUE(stationary.isInside(infp));
|
||||||
|
|
||||||
|
for(auto v : infp) {
|
||||||
|
auto dx = getX(v) - getX(vo);
|
||||||
|
auto dy = getY(v) - getY(vo);
|
||||||
|
|
||||||
|
Item tmp = orbiter;
|
||||||
|
|
||||||
|
tmp.translate({dx, dy});
|
||||||
|
|
||||||
|
bool notinside = !tmp.isInside(stationary);
|
||||||
|
bool notintersecting = !Item::intersects(tmp, stationary) ||
|
||||||
|
Item::touches(tmp, stationary);
|
||||||
|
|
||||||
|
if(!(notinside && notintersecting)) {
|
||||||
|
std::vector<std::reference_wrapper<Item>> inp = {
|
||||||
|
std::ref(stationary), std::ref(tmp), std::ref(infp)
|
||||||
|
};
|
||||||
|
|
||||||
|
exportfun(inp, bin, testcase*i++);
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_TRUE(notintersecting);
|
||||||
|
ASSERT_TRUE(notinside);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for(auto& td : nfp_testdata) {
|
||||||
|
auto orbiter = td.orbiter;
|
||||||
|
auto stationary = td.stationary;
|
||||||
|
onetest(orbiter, stationary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for(auto& td : nfp_testdata) {
|
||||||
|
auto orbiter = td.stationary;
|
||||||
|
auto stationary = td.orbiter;
|
||||||
|
onetest(orbiter, stationary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
#include <boost/nowide/iostream.hpp>
|
#include <boost/nowide/iostream.hpp>
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
|
|
||||||
|
#include <benchmark.h>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
unsigned int Model::s_auto_extruder_id = 1;
|
unsigned int Model::s_auto_extruder_id = 1;
|
||||||
@ -378,8 +380,32 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb,
|
|||||||
// Create the arranger config
|
// Create the arranger config
|
||||||
auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
|
auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
|
||||||
|
|
||||||
|
Benchmark bench;
|
||||||
|
|
||||||
|
std::cout << "Creating model siluett..." << std::endl;
|
||||||
|
|
||||||
|
bench.start();
|
||||||
// Get the 2D projected shapes with their 3D model instance pointers
|
// Get the 2D projected shapes with their 3D model instance pointers
|
||||||
auto shapemap = arr::projectModelFromTop(model);
|
auto shapemap = arr::projectModelFromTop(model);
|
||||||
|
bench.stop();
|
||||||
|
|
||||||
|
std::cout << "Model siluett created in " << bench.getElapsedSec()
|
||||||
|
<< " seconds. " << "Min object distance = " << min_obj_distance << std::endl;
|
||||||
|
|
||||||
|
// std::cout << "{" << std::endl;
|
||||||
|
// std::for_each(shapemap.begin(), shapemap.end(),
|
||||||
|
// [] (ShapeData2D::value_type& it)
|
||||||
|
// {
|
||||||
|
// std::cout << "\t{" << std::endl;
|
||||||
|
// Item& item = it.second;
|
||||||
|
// for(auto& v : item) {
|
||||||
|
// std::cout << "\t\t" << "{" << getX(v)
|
||||||
|
// << ", " << getY(v) << "},\n";
|
||||||
|
// }
|
||||||
|
// std::cout << "\t}," << std::endl;
|
||||||
|
// });
|
||||||
|
// std::cout << "}" << std::endl;
|
||||||
|
// return true;
|
||||||
|
|
||||||
double area = 0;
|
double area = 0;
|
||||||
double area_max = 0;
|
double area_max = 0;
|
||||||
@ -427,15 +453,23 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb,
|
|||||||
|
|
||||||
// Will use the DJD selection heuristic with the BottomLeft placement
|
// Will use the DJD selection heuristic with the BottomLeft placement
|
||||||
// strategy
|
// strategy
|
||||||
using Arranger = Arranger<BottomLeftPlacer, DJDHeuristic>;
|
using Arranger = Arranger<NfpPlacer, DJDHeuristic>;
|
||||||
|
|
||||||
Arranger arranger(bin, min_obj_distance);
|
Arranger::PlacementConfig pcfg;
|
||||||
|
pcfg.alignment = Arranger::PlacementConfig::Alignment::BOTTOM_LEFT;
|
||||||
|
Arranger arranger(bin, min_obj_distance, pcfg);
|
||||||
|
|
||||||
|
std::cout << "Arranging model..." << std::endl;
|
||||||
|
bench.start();
|
||||||
// Arrange and return the items with their respective indices within the
|
// Arrange and return the items with their respective indices within the
|
||||||
// input sequence.
|
// input sequence.
|
||||||
ArrangeResult result =
|
ArrangeResult result =
|
||||||
arranger.arrangeIndexed(shapes.begin(), shapes.end());
|
arranger.arrangeIndexed(shapes.begin(), shapes.end());
|
||||||
|
|
||||||
|
bench.stop();
|
||||||
|
std::cout << "Model arranged in " << bench.getElapsedSec()
|
||||||
|
<< " seconds." << std::endl;
|
||||||
|
|
||||||
|
|
||||||
auto applyResult = [&shapemap](ArrangeResult::value_type& group,
|
auto applyResult = [&shapemap](ArrangeResult::value_type& group,
|
||||||
Coord batch_offset)
|
Coord batch_offset)
|
||||||
@ -464,6 +498,8 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::cout << "Applying result..." << std::endl;
|
||||||
|
bench.start();
|
||||||
if(first_bin_only) {
|
if(first_bin_only) {
|
||||||
applyResult(result.front(), 0);
|
applyResult(result.front(), 0);
|
||||||
} else {
|
} else {
|
||||||
@ -477,6 +513,9 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb,
|
|||||||
batch_offset += static_cast<Coord>(2*bin.width()*SCALING_FACTOR);
|
batch_offset += static_cast<Coord>(2*bin.width()*SCALING_FACTOR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bench.stop();
|
||||||
|
std::cout << "Result applied in " << bench.getElapsedSec()
|
||||||
|
<< " seconds." << std::endl;
|
||||||
|
|
||||||
for(auto objptr : model.objects) objptr->invalidate_bounding_box();
|
for(auto objptr : model.objects) objptr->invalidate_bounding_box();
|
||||||
|
|
||||||
@ -490,7 +529,7 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
|
|||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
if(bb != nullptr && bb->defined) {
|
if(bb != nullptr && bb->defined) {
|
||||||
const bool FIRST_BIN_ONLY = true;
|
const bool FIRST_BIN_ONLY = false;
|
||||||
ret = arr::arrange(*this, dist, bb, FIRST_BIN_ONLY);
|
ret = arr::arrange(*this, dist, bb, FIRST_BIN_ONLY);
|
||||||
} else {
|
} else {
|
||||||
// get the (transformed) size of each instance so that we take
|
// get the (transformed) size of each instance so that we take
|
||||||
|
Loading…
x
Reference in New Issue
Block a user