initial NFP method with convex polygons working.

This commit is contained in:
tamasmeszaros 2018-06-05 12:02:45 +02:00
parent fd829580e9
commit 34c850fa9d
23 changed files with 3865 additions and 774 deletions

View File

@ -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
View 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_ */

View File

@ -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()

View File

@ -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/

View File

@ -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>;

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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;
}
}; };
} }

View File

@ -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

View File

@ -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>(

View File

@ -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});
} }
}; };
} }
} }

View File

@ -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_;
}; };

View File

@ -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, &not_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());
}); // });
} }
}; };

View File

@ -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){};
}; };
} }

View File

@ -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})

View File

@ -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

View File

@ -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

View 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

View File

@ -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) {

View File

@ -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