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_BUILD_SHARED_LIB OFF CACHE BOOL "Disable build of shared lib.")
if(LIBNEST2D_UNITTESTS)
# 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})
# Add the binpack2d main sources and link them to libslic3r
target_link_libraries(libslic3r libnest2d)
target_link_libraries(libslic3r libnest2d_static)
# ##############################################################################
# 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
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
# "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")
if(NOT Boost_INCLUDE_DIRS_FOUND)
find_package(Boost REQUIRED)
find_package(Boost 1.58 REQUIRED)
# TODO automatic download of boost geometry headers
endif()
@ -65,9 +68,19 @@ else()
message(STATUS "Libnest2D backend is: ${LIBNEST2D_GEOMETRIES_TARGET}")
endif()
add_library(libnest2d STATIC ${LIBNEST2D_SRCFILES} )
target_link_libraries(libnest2d ${LIBNEST2D_GEOMETRIES_TARGET})
target_include_directories(libnest2d PUBLIC ${CMAKE_SOURCE_DIR})
message(STATUS "clipper lib is: ${LIBNEST2D_GEOMETRIES_TARGET}")
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})
@ -79,3 +92,12 @@ endif()
if(LIBNEST2D_UNITTESTS)
add_subdirectory(tests)
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/
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/include/
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/include/polyclipping/
${CMAKE_PREFIX_PATH}/include/polyclipping
${CMAKE_PREFIX_PATH}/include/
/opt/local/include/
/opt/local/include/polyclipping/
/usr/local/include/
@ -29,6 +31,8 @@ FIND_LIBRARY(CLIPPER_LIBRARIES polyclipping
$ENV{CLIPPER_PATH}/lib/polyclipping/
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/lib/
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/lib/polyclipping/
${CMAKE_PREFIX_PATH}/lib/
${CMAKE_PREFIX_PATH}/lib/polyclipping/
/opt/local/lib/
/opt/local/lib/polyclipping/
/usr/local/lib/

View File

@ -29,6 +29,7 @@ using FillerSelection = strategies::_FillerSelection<PolygonImpl>;
using FirstFitSelection = strategies::_FirstFitSelection<PolygonImpl>;
using DJDHeuristic = strategies::_DJDHeuristic<PolygonImpl>;
using NfpPlacer = strategies::_NofitPolyPlacer<PolygonImpl>;
using BottomLeftPlacer = strategies::_BottomLeftPlacer<PolygonImpl>;
using NofitPolyPlacer = strategies::_NofitPolyPlacer<PolygonImpl>;

View File

@ -25,12 +25,13 @@ using libnest2d::setX;
using libnest2d::setY;
using Box = libnest2d::_Box<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
* models of the geometries remain the same if a conforming model for binpack2d
* 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 libnest2d
* 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
* 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 {};
@ -269,17 +270,34 @@ struct interior_rings<bp2d::PolygonImpl> {
}
};
/* ************************************************************************** */
/* MultiPolygon concept adaptation ****************************************** */
/* ************************************************************************** */
template<> struct tag<bp2d::Shapes> {
using type = multi_polygon_tag;
};
} // traits
} // geometry
// This is an addition to the ring implementation
// This is an addition to the ring implementation of Polygon concept
template<>
struct range_value<bp2d::PathImpl> {
using type = bp2d::PointImpl;
};
template<>
struct range_value<bp2d::Shapes> {
using type = bp2d::PolygonImpl;
};
} // boost
/* ************************************************************************** */
/* Algorithms *************************************************************** */
/* ************************************************************************** */
namespace libnest2d { // Now the algorithms that boost can provide...
template<>
@ -296,7 +314,7 @@ inline double PointLike::distance(const PointImpl& p,
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<>
inline bool ShapeLike::intersects(const PathImpl& sh1,
const PathImpl& sh2)
@ -304,14 +322,14 @@ inline bool ShapeLike::intersects(const PathImpl& sh1,
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<>
inline bool ShapeLike::intersects(const PolygonImpl& sh1,
const PolygonImpl& 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<>
inline bool ShapeLike::intersects(const bp2d::Segment& s1,
const bp2d::Segment& s2) {
@ -346,6 +364,7 @@ inline bool ShapeLike::touches( const PolygonImpl& sh1,
return boost::geometry::touches(sh1, sh2);
}
#ifndef DISABLE_BOOST_BOUNDING_BOX
template<>
inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh) {
bp2d::Box b;
@ -354,7 +373,34 @@ inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh) {
}
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;
PolygonImpl cpy = sh;
@ -364,8 +410,10 @@ inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) {
boost::geometry::transform(cpy, sh, rotate);
}
#ifndef DISABLE_BOOST_TRANSLATE
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;
PolygonImpl cpy = sh;
@ -374,19 +422,33 @@ inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs) {
boost::geometry::transform(cpy, sh, translate);
}
#endif
#ifndef DISABLE_BOOST_OFFSET
template<>
inline void ShapeLike::offset(PolygonImpl& sh, bp2d::Coord distance) {
inline void ShapeLike::offset(PolygonImpl& sh, bp2d::Coord distance)
{
PolygonImpl cpy = sh;
boost::geometry::buffer(cpy, sh, distance);
}
#endif
#ifndef DISABLE_BOOST_NFP_MERGE
template<>
inline bp2d::Shapes Nfp::merge(const bp2d::Shapes& shapes,
const PolygonImpl& sh)
{
bp2d::Shapes retv;
boost::geometry::union_(shapes, sh, retv);
return retv;
}
#endif
#ifndef DISABLE_BOOST_MINKOWSKI_ADD
template<>
inline PolygonImpl& Nfp::minkowskiAdd(PolygonImpl& sh,
const PolygonImpl& /*other*/) {
const PolygonImpl& /*other*/)
{
return sh;
}
#endif
@ -394,12 +456,26 @@ inline PolygonImpl& Nfp::minkowskiAdd(PolygonImpl& sh,
#ifndef DISABLE_BOOST_SERIALIZE
template<>
inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>(
const PolygonImpl& sh)
const PolygonImpl& sh, double scale)
{
std::stringstream ss;
std::string style = "fill: orange; stroke: black; stroke-width: 1px;";
auto svg_data = boost::geometry::svg(sh, style);
std::string style = "fill: none; stroke: black; stroke-width: 1px;";
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;
@ -417,7 +493,8 @@ inline void ShapeLike::unserialize<libnest2d::Formats::SVG>(
#endif
template<> inline std::pair<bool, std::string>
ShapeLike::isValid(const PolygonImpl& sh) {
ShapeLike::isValid(const PolygonImpl& sh)
{
std::string 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.
find_package(Clipper QUIET)
find_package(Clipper 6.1)
if(NOT CLIPPER_FOUND)
find_package(Subversion QUIET)

View File

@ -46,10 +46,25 @@ std::string ShapeLike::toString(const PolygonImpl& sh)
return ss.str();
}
template<> PolygonImpl ShapeLike::create( std::initializer_list< PointImpl > il)
template<> PolygonImpl ShapeLike::create( const PathImpl& path )
{
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
// direction

View File

@ -20,6 +20,7 @@ using PolygonImpl = ClipperLib::PolyNode;
using PathImpl = ClipperLib::Path;
inline PointImpl& operator +=(PointImpl& p, const PointImpl& pa ) {
// This could be done with SIMD
p.X += pa.X;
p.Y += pa.Y;
return p;
@ -37,6 +38,13 @@ inline PointImpl& operator -=(PointImpl& p, const PointImpl& pa ) {
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) {
PointImpl ret = p1;
ret -= p2;
@ -100,14 +108,29 @@ inline void ShapeLike::reserve(PolygonImpl& sh, unsigned long 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
template<>
inline double ShapeLike::area(const PolygonImpl& sh) {
#define DISABLE_BOOST_AREA
double ret = ClipperLib::Area(sh.Contour);
// if(OrientationType<PolygonImpl>::Value == Orientation::COUNTER_CLOCKWISE)
// ret = -ret;
return ret;
return _smartarea::area<OrientationType<PolygonImpl>::Value>(sh);
}
template<>
@ -191,8 +214,9 @@ template<> struct HolesContainer<PolygonImpl> {
using Type = ClipperLib::Paths;
};
template<>
PolygonImpl ShapeLike::create( std::initializer_list< PointImpl > il);
template<> PolygonImpl ShapeLike::create( const PathImpl& path);
template<> PolygonImpl ShapeLike::create( PathImpl&& path);
template<>
const THolesContainer<PolygonImpl>& ShapeLike::holes(
@ -202,33 +226,94 @@ template<>
THolesContainer<PolygonImpl>& ShapeLike::holes(PolygonImpl& sh);
template<>
inline TCountour<PolygonImpl>& ShapeLike::getHole(PolygonImpl& sh,
inline TContour<PolygonImpl>& ShapeLike::getHole(PolygonImpl& sh,
unsigned long idx)
{
return sh.Childs[idx]->Contour;
}
template<>
inline const TCountour<PolygonImpl>& ShapeLike::getHole(const PolygonImpl& sh,
unsigned long idx) {
inline const TContour<PolygonImpl>& ShapeLike::getHole(const PolygonImpl& sh,
unsigned long idx)
{
return sh.Childs[idx]->Contour;
}
template<>
inline size_t ShapeLike::holeCount(const PolygonImpl& sh) {
template<> inline size_t ShapeLike::holeCount(const PolygonImpl& sh)
{
return sh.Childs.size();
}
template<>
inline PathImpl& ShapeLike::getContour(PolygonImpl& sh) {
template<> inline PathImpl& ShapeLike::getContour(PolygonImpl& sh)
{
return sh.Contour;
}
template<>
inline const PathImpl& ShapeLike::getContour(const PolygonImpl& sh) {
inline const PathImpl& ShapeLike::getContour(const PolygonImpl& sh)
{
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

View File

@ -3,18 +3,56 @@
#include "geometry_traits.hpp"
#include <algorithm>
#include <vector>
namespace libnest2d {
struct Nfp {
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,
"ShapeLike::minkowskiAdd() unimplemented!");
"Nfp::minkowskiAdd() unimplemented!");
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>
static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) {
auto isConvex = [](const RawShape& sh) {
@ -31,24 +69,20 @@ static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) {
{
RawShape other = cother;
// Make it counter-clockwise
for(auto shit = ShapeLike::begin(other);
shit != ShapeLike::end(other); ++shit ) {
auto& v = *shit;
setX(v, -getX(v));
setY(v, -getY(v));
}
// Make the other polygon counter-clockwise
std::reverse(ShapeLike::begin(other), ShapeLike::end(other));
RawShape rsh;
RawShape rsh; // Final nfp placeholder
std::vector<Edge> edgelist;
size_t cap = ShapeLike::contourVertexCount(sh) +
ShapeLike::contourVertexCount(other);
// Reserve the needed memory
edgelist.reserve(cap);
ShapeLike::reserve(rsh, cap);
{
{ // place all edges from sh into edgelist
auto first = ShapeLike::cbegin(sh);
auto next = first + 1;
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++));
}
{
{ // place all edges from other into edgelist
auto first = ShapeLike::cbegin(other);
auto next = first + 1;
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++));
}
// Sort the edges by angle to X axis.
std::sort(edgelist.begin(), edgelist.end(),
[](const Edge& e1, const Edge& e2)
{
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().second());
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());
eit != edgelist.end();
++eit) {
++eit)
{
auto d = *tmp - eit->first();
auto p = eit->second() + d;
auto dx = getX(*tmp) - getX(eit->first());
auto dy = getY(*tmp) - getY(eit->first());
ShapeLike::addVertex(rsh, getX(eit->second())+dx,
getY(eit->second())+dy );
ShapeLike::addVertex(rsh, p);
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;
};
@ -118,6 +185,36 @@ static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) {
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 <array>
#include <vector>
#include <numeric>
#include <limits>
#include "common.hpp"
@ -81,6 +82,8 @@ public:
inline TCoord<RawPoint> width() const BP2D_NOEXCEPT;
inline TCoord<RawPoint> height() const BP2D_NOEXCEPT;
inline RawPoint center() const BP2D_NOEXCEPT;
};
template<class RawPoint>
@ -102,8 +105,9 @@ public:
inline Radians angleToXaxis() const;
};
class PointLike {
public:
// This struct serves as a namespace. The only difference is that is can be
// used in friend declarations.
struct PointLike {
template<class RawPoint>
static TCoord<RawPoint> x(const RawPoint& p)
@ -133,7 +137,7 @@ public:
static double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/)
{
static_assert(always_false<RawPoint>::value,
"PointLike::distance(point, point) unimplemented");
"PointLike::distance(point, point) unimplemented!");
return 0;
}
@ -142,7 +146,7 @@ public:
const _Segment<RawPoint>& /*s*/)
{
static_assert(always_false<RawPoint>::value,
"PointLike::distance(point, segment) unimplemented");
"PointLike::distance(point, segment) unimplemented!");
return 0;
}
@ -229,21 +233,38 @@ void setY(RawPoint& p, const TCoord<RawPoint>& val) {
template<class RawPoint>
inline Radians _Segment<RawPoint>::angleToXaxis() const
{
static const double Pi_2 = 2*Pi;
TCoord<RawPoint> dx = getX(second()) - getX(first());
TCoord<RawPoint> dy = getY(second()) - getY(first());
if(dx == 0 && dy >= 0) return Pi/2;
if(dx == 0 && dy < 0) return 3*Pi/2;
if(dy == 0 && dx >= 0) return 0;
if(dy == 0 && dx < 0) return Pi;
double a = std::atan2(dy, dx);
// if(dx == 0 && dy >= 0) return Pi/2;
// if(dx == 0 && dy < 0) return 3*Pi/2;
// if(dy == 0 && dx >= 0) return 0;
// if(dy == 0 && dx < 0) return Pi;
double ddx = static_cast<double>(dx);
auto s = std::signbit(ddx);
double a = std::atan(ddx/dy);
if(s) a += Pi;
// double ddx = static_cast<double>(dx);
auto s = std::signbit(a);
// double a = std::atan(ddx/dy);
if(s) a += Pi_2;
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>
struct HolesContainer {
using Type = std::vector<RawShape>;
@ -258,7 +279,7 @@ struct CountourType {
};
template<class RawShape>
using TCountour = typename CountourType<remove_cvref_t<RawShape>>::Type;
using TContour = typename CountourType<remove_cvref_t<RawShape>>::Type;
enum class Orientation {
CLOCKWISE,
@ -277,12 +298,23 @@ enum class Formats {
SVG
};
// This struct serves as a namespace. The only difference is that is can be
// used in friend declarations.
struct ShapeLike {
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
@ -319,25 +351,6 @@ struct ShapeLike {
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>
static std::string toString(const RawShape& /*sh*/)
{
@ -345,10 +358,10 @@ struct ShapeLike {
}
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,
"ShapeLike::serialize() unimplemented");
"ShapeLike::serialize() unimplemented!");
return "";
}
@ -356,21 +369,14 @@ struct ShapeLike {
static void unserialize(RawShape& /*sh*/, const std::string& /*str*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::unserialize() unimplemented");
"ShapeLike::unserialize() unimplemented!");
}
template<class RawShape>
static double area(const RawShape& /*sh*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::area() unimplemented");
return 0;
}
template<class RawShape>
static double area(const _Box<TPoint<RawShape>>& box)
{
return box.width() * box.height();
"ShapeLike::area() unimplemented!");
return 0;
}
@ -378,7 +384,7 @@ struct ShapeLike {
static bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::intersects() unimplemented");
"ShapeLike::intersects() unimplemented!");
return false;
}
@ -387,7 +393,7 @@ struct ShapeLike {
const RawShape& /*shape*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::isInside(point, shape) unimplemented");
"ShapeLike::isInside(point, shape) unimplemented!");
return false;
}
@ -396,7 +402,7 @@ struct ShapeLike {
const RawShape& /*shape*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::isInside(shape, shape) unimplemented");
"ShapeLike::isInside(shape, shape) unimplemented!");
return false;
}
@ -405,7 +411,7 @@ struct ShapeLike {
const RawShape& /*shape*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::touches(shape, shape) unimplemented");
"ShapeLike::touches(shape, shape) unimplemented!");
return false;
}
@ -413,13 +419,30 @@ struct ShapeLike {
static _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::boundingBox(shape) unimplemented");
"ShapeLike::boundingBox(shape) unimplemented!");
}
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>
@ -437,13 +460,13 @@ struct ShapeLike {
}
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];
}
template<class RawShape>
static const TCountour<RawShape>& getHole(const RawShape& sh,
static const TContour<RawShape>& getHole(const RawShape& sh,
unsigned long idx)
{
return holes(sh)[idx];
@ -456,13 +479,13 @@ struct ShapeLike {
}
template<class RawShape>
static TCountour<RawShape>& getContour(RawShape& sh)
static TContour<RawShape>& getContour(RawShape& sh)
{
return sh;
}
template<class RawShape>
static const TCountour<RawShape>& getContour(const RawShape& sh)
static const TContour<RawShape>& getContour(const RawShape& sh)
{
return sh;
}
@ -471,14 +494,14 @@ struct ShapeLike {
static void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::rotate() unimplemented");
"ShapeLike::rotate() unimplemented!");
}
template<class RawShape, class RawPoint>
static void translate(RawShape& /*sh*/, const RawPoint& /*offs*/)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::translate() unimplemented");
"ShapeLike::translate() unimplemented!");
}
template<class RawShape>
@ -490,12 +513,96 @@ struct ShapeLike {
template<class RawShape>
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

View File

@ -97,6 +97,12 @@ public:
inline _Item(const std::initializer_list< Vertex >& 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
* on the implementation of the polygon.
@ -174,7 +180,7 @@ public:
double ret ;
if(area_cache_valid_) ret = area_cache_;
else {
ret = std::abs(ShapeLike::area(offsettedShape()));
ret = ShapeLike::area(offsettedShape());
area_cache_ = ret;
area_cache_valid_ = true;
}
@ -201,6 +207,8 @@ public:
return ShapeLike::isInside(transformedShape(), sh.transformedShape());
}
inline bool isInside(const _Box<TPoint<RawShape>>& box);
inline void translate(const Vertex& d) BP2D_NOEXCEPT
{
translation_ += d; has_translation_ = true;
@ -328,7 +336,7 @@ class _Rectangle: public _Item<RawShape> {
using TO = Orientation;
public:
using Unit = TCoord<RawShape>;
using Unit = TCoord<TPoint<RawShape>>;
template<TO o = OrientationType<RawShape>::Value>
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.
*
@ -481,8 +495,18 @@ public:
/// Clear the packed items so a new session can be started.
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.
*/
@ -508,6 +532,14 @@ public:
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.
*
@ -614,7 +646,7 @@ public:
private:
BinType bin_;
PlacementConfig pconfig_;
TCoord<typename Item::ShapeType> min_obj_distance_;
Unit min_obj_distance_;
using SItem = typename SelectionStrategy::Item;
using TPItem = remove_cvref_t<Item>;
@ -680,6 +712,21 @@ public:
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:
template<class TIterator,
@ -694,14 +741,7 @@ private:
inline PackGroup _arrange(TIterator from, TIterator to, bool = false)
{
__arrange(from, to);
PackGroup ret;
for(size_t i = 0; i < selector_.binCount(); i++) {
auto items = selector_.itemsForBin(i);
ret.push_back(items);
}
return ret;
return lastResult();
}
template<class TIterator,
@ -713,14 +753,7 @@ private:
item_cache_ = {from, to};
__arrange(item_cache_.begin(), item_cache_.end());
PackGroup ret;
for(size_t i = 0; i < selector_.binCount(); i++) {
auto items = selector_.itemsForBin(i);
ret.push_back(items);
}
return ret;
return lastResult();
}
template<class TIterator,
@ -784,7 +817,7 @@ private:
template<class TIter> inline void __arrange(TIter from, TIter to)
{
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>(

View File

@ -2,29 +2,195 @@
#define NOFITPOLY_HPP
#include "placer_boilerplate.hpp"
#include "../geometries_nfp.hpp"
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>
class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
RawShape, _Box<TPoint<RawShape>>> {
RawShape, _Box<TPoint<RawShape>>, NfpPConfig<RawShape>> {
using Base = PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
RawShape, _Box<TPoint<RawShape>>>;
RawShape, _Box<TPoint<RawShape>>, NfpPConfig<RawShape>>;
DECLARE_PLACER(Base)
using Box = _Box<TPoint<RawShape>>;
public:
inline explicit _NofitPolyPlacer(const BinType& bin): Base(bin) {}
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(); }
inline ItemGroup getItems() { return items_; }
inline ItemGroup getItems() const { return items_; }
inline void clearItems() { items_.clear(); }
#ifndef NDEBUG
std::vector<Item> debug_items_;
#endif
protected:
BinType bin_;
Container items_;
Cfg config_;
};

View File

@ -84,9 +84,11 @@ public:
bool try_reverse = config_.try_reverse_order;
// 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);
packed_bins_.emplace_back();
placers.back().configure(pconfig);
free_area = ShapeLike::area<RawShape>(bin);
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()) {
auto& placer = placers.back();
@ -472,30 +484,37 @@ public:
free_area = bin_area - filled_area;
auto itmp = it++;
not_packed.erase(itmp);
makeProgress(placer);
} else it++;
}
}
// try pieses one by one
while(tryOneByOne(placer, waste))
while(tryOneByOne(placer, waste)) {
waste = 0;
makeProgress(placer);
}
// try groups of 2 pieses
while(tryGroupsOfTwo(placer, waste))
while(tryGroupsOfTwo(placer, waste)) {
waste = 0;
makeProgress(placer);
}
// try groups of 3 pieses
while(tryGroupsOfThree(placer, waste))
while(tryGroupsOfThree(placer, waste)) {
waste = 0;
makeProgress(placer);
}
if(waste < free_area) waste += w;
else if(!not_packed.empty()) addBin();
}
std::for_each(placers.begin(), placers.end(),
[this](Placer& placer){
packed_bins_.push_back(placer.getItems());
});
// std::for_each(placers.begin(), placers.end(),
// [this](Placer& placer){
// packed_bins_.push_back(placer.getItems());
// });
}
};

View File

@ -26,8 +26,14 @@ public:
return packed_bins_[binIndex];
}
inline void progressIndicator(ProgressFunction fn) {
progress_ = fn;
}
protected:
PackGroup packed_bins_;
ProgressFunction progress_ = [](unsigned){};
};
}

View File

@ -1,10 +1,11 @@
# Try to find existing GTest installation
find_package(GTest QUIET)
find_package(GTest 1.7)
if(NOT GTEST_FOUND)
message(STATUS "GTest not found so downloading...")
# 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)
set(UPDATE_DISCONNECTED_IF_AVAILABLE "")
@ -15,7 +16,7 @@ if(NOT GTEST_FOUND)
include(DownloadProject)
download_project(PROJ googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.8.0
GIT_TAG release-1.7.0
${UPDATE_DISCONNECTED_IF_AVAILABLE}
)
@ -27,22 +28,20 @@ if(NOT GTEST_FOUND)
${googletest_BINARY_DIR}
)
set(GTEST_INCLUDE_DIRS ${googletest_SOURCE_DIR}/include)
else()
include_directories(${GTEST_INCLUDE_DIRS} )
find_package(Threads REQUIRED)
set(GTEST_LIBS_TO_LINK ${GTEST_BOTH_LIBRARIES} Threads::Threads)
endif()
include_directories(BEFORE ${LIBNEST2D_HEADERS})
add_executable(bp2d_tests test.cpp printer_parts.h printer_parts.cpp)
target_link_libraries(bp2d_tests libnest2d
${GTEST_LIBRARIES}
)
add_executable(bp2d_tests test.cpp svgtools.hpp printer_parts.h printer_parts.cpp)
target_link_libraries(bp2d_tests libnest2d_static ${GTEST_LIBS_TO_LINK} )
target_include_directories(bp2d_tests PRIVATE BEFORE ${LIBNEST2D_HEADERS}
${GTEST_INCLUDE_DIRS})
if(DEFINED LIBNEST2D_TEST_LIBRARIES)
target_link_libraries(bp2d_tests ${LIBNEST2D_TEST_LIBRARIES})
endif()
add_test(gtests 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})
add_test(libnest2d_tests bp2d_tests)

View File

@ -7,232 +7,56 @@
#include "printer_parts.h"
#include "benchmark.h"
#include "svgtools.hpp"
namespace {
using namespace libnest2d;
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
//using PackGroup = std::vector<ItemGroup>;
template<int SCALE, class Bin >
void exportSVG(PackGroup& result, const Bin& bin) {
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 = 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++;
std::vector<Item>& _parts(std::vector<Item>& ret, const TestData& data)
{
if(ret.empty()) {
ret.reserve(data.size());
for(auto& inp : data)
ret.emplace_back(inp);
}
return ret;
}
template< int SCALE, class Bin>
void exportSVG(ItemGroup& result, const Bin& bin, int idx) {
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>& prusaParts() {
static std::vector<Item> ret;
return _parts(ret, PRINTER_PART_POLYGONS);
}
void findDegenerateCase() {
using namespace libnest2d;
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++;
}
std::vector<Item>& stegoParts() {
static std::vector<Item> ret;
return _parts(ret, STEGOSAUR_POLYGONS);
}
void arrangeRectangles() {
using namespace libnest2d;
// 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;
auto input = stegoParts();
const int SCALE = 1000000;
// const int SCALE = 1;
Box bin(210*SCALE, 250*SCALE);
auto scaler = [&SCALE, &bin](Item& item) {
// 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 = 0; //6*SCALE;
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);
Arranger<BottomLeftPlacer, DJDHeuristic> arrange(bin, min_obj_distance);
// arrange.progressIndicator([&arrange, &bin](unsigned r){
// svg::SVGWriter::Config conf;
// conf.mm_in_coord_units = SCALE;
// svg::SVGWriter svgw(conf);
// svgw.setSize(bin);
// svgw.writePackGroup(arrange.lastResult());
// svgw.save("out");
// std::cout << "Remaining items: " << r << std::endl;
// });
Benchmark bench;
@ -249,8 +73,12 @@ void arrangeRectangles() {
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*/) {

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,11 @@
#define PRINTER_PARTS_H
#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

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 "gmock/gmock.h"
#include <gtest/gtest.h>
#include <fstream>
#include <libnest2d.h>
@ -7,6 +6,17 @@
#include <libnest2d/geometries_io.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)
{
@ -24,6 +34,44 @@ TEST(BasicFunctionality, Angles)
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
@ -33,21 +81,21 @@ TEST(BasicFunctionality, creationAndDestruction)
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} });
ASSERT_EQ(sh2.vertexCount(), 4);
ASSERT_EQ(sh2.vertexCount(), 4u);
// copy
Item sh3 = sh2;
ASSERT_EQ(sh3.vertexCount(), 4);
ASSERT_EQ(sh3.vertexCount(), 4u);
sh2 = {};
ASSERT_EQ(sh2.vertexCount(), 0);
ASSERT_EQ(sh3.vertexCount(), 4);
ASSERT_EQ(sh2.vertexCount(), 0u);
ASSERT_EQ(sh3.vertexCount(), 4u);
}
@ -70,7 +118,8 @@ TEST(GeometryAlgorithms, Distance) {
auto check = [](Coord val, Coord expected) {
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
ASSERT_EQ(val, expected);
};
@ -112,6 +161,18 @@ TEST(GeometryAlgorithms, Area) {
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) {
@ -240,7 +301,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight)
auto groups = arrange(rects.begin(), rects.end());
ASSERT_EQ(groups.size(), 1);
ASSERT_EQ(groups.size(), 1u);
ASSERT_EQ(groups[0].size(), rects.size());
// check for no intersections, no containment:
@ -294,7 +355,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose)
auto groups = arrange(rects.begin(), rects.end());
ASSERT_EQ(groups.size(), 1);
ASSERT_EQ(groups.size(), 1u);
ASSERT_EQ(groups[0].size(), rects.size());
// check for no intersections, no containment:
@ -363,7 +424,7 @@ R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
TEST(GeometryAlgorithms, BottomLeftStressTest) {
using namespace libnest2d;
auto input = PRINTER_PART_POLYGONS;
auto& input = prusaParts();
Box bin(210, 250);
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) {
using namespace libnest2d;
const unsigned long SCALE = 1;
const Coord SCALE = 1000000;
Box bin(210*SCALE, 250*SCALE);
Item stationary = {
{120, 114},
{130, 114},
{130, 103},
{128, 96},
{122, 96},
{120, 103},
{120, 114}
};
int testcase = 0;
Item orbiter = {
{72, 147},
{94, 151},
{178, 151},
{178, 59},
{72, 59},
{72, 147}
};
auto& exportfun = exportSVG<1, Box>;
orbiter.translate({210*SCALE, 0});
auto onetest = [&](Item& orbiter, Item& stationary){
testcase++;
auto&& nfp = Nfp::noFitPolygon(stationary.rawShape(),
orbiter.transformedShape());
orbiter.translate({210*SCALE, 0});
auto v = ShapeLike::isValid(nfp);
auto&& nfp = Nfp::noFitPolygon(stationary.rawShape(),
orbiter.transformedShape());
if(!v.first) {
std::cout << v.second << std::endl;
}
auto v = ShapeLike::isValid(nfp);
ASSERT_TRUE(v.first);
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++);
if(!v.first) {
std::cout << v.second << std::endl;
}
//ASSERT_TRUE(notintersecting);
ASSERT_TRUE(notinside);
ASSERT_TRUE(v.first);
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) {

View File

@ -20,6 +20,8 @@
#include <boost/nowide/iostream.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <benchmark.h>
namespace Slic3r {
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
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
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_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
// 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
// input sequence.
ArrangeResult result =
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,
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) {
applyResult(result.front(), 0);
} 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);
}
}
bench.stop();
std::cout << "Result applied in " << bench.getElapsedSec()
<< " seconds." << std::endl;
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;
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);
} else {
// get the (transformed) size of each instance so that we take