mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-05-08 13:49:10 +08:00
initial NFP method with convex polygons working.
This commit is contained in:
parent
fd829580e9
commit
34c850fa9d
@ -684,6 +684,7 @@ add_custom_target(pot
|
||||
# ##############################################################################
|
||||
|
||||
set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d")
|
||||
set(LIBNEST2D_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
58
xs/src/benchmark.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) Tamás Mészáros
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
#ifndef INCLUDE_BENCHMARK_H_
|
||||
#define INCLUDE_BENCHMARK_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <ratio>
|
||||
|
||||
/**
|
||||
* A class for doing benchmarks.
|
||||
*/
|
||||
class Benchmark {
|
||||
typedef std::chrono::high_resolution_clock Clock;
|
||||
typedef Clock::duration Duration;
|
||||
typedef Clock::time_point TimePoint;
|
||||
|
||||
TimePoint t1, t2;
|
||||
Duration d;
|
||||
|
||||
inline double to_sec(Duration d) {
|
||||
return d.count() * double(Duration::period::num) / Duration::period::den;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Measure time from the moment of this call.
|
||||
*/
|
||||
void start() { t1 = Clock::now(); }
|
||||
|
||||
/**
|
||||
* Measure time to the moment of this call.
|
||||
*/
|
||||
void stop() { t2 = Clock::now(); }
|
||||
|
||||
/**
|
||||
* Get the time elapsed between a start() end a stop() call.
|
||||
* @return Returns the elapsed time in seconds.
|
||||
*/
|
||||
double getElapsedSec() { d = t2 - t1; return to_sec(d); }
|
||||
};
|
||||
|
||||
|
||||
#endif /* INCLUDE_BENCHMARK_H_ */
|
@ -19,6 +19,9 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/)
|
||||
option(LIBNEST2D_UNITTESTS "If enabled, googletest framework will be downloaded
|
||||
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()
|
||||
|
@ -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/
|
||||
|
@ -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>;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>(
|
||||
|
@ -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});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -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, ¬_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());
|
||||
// });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -26,8 +26,14 @@ public:
|
||||
return packed_bins_[binIndex];
|
||||
}
|
||||
|
||||
inline void progressIndicator(ProgressFunction fn) {
|
||||
progress_ = fn;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
PackGroup packed_bins_;
|
||||
ProgressFunction progress_ = [](unsigned){};
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
@ -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
|
||||
|
112
xs/src/libnest2d/tests/svgtools.hpp
Normal file
112
xs/src/libnest2d/tests/svgtools.hpp
Normal file
@ -0,0 +1,112 @@
|
||||
#ifndef SVGTOOLS_HPP
|
||||
#define SVGTOOLS_HPP
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include <libnest2d.h>
|
||||
#include <libnest2d/geometries_io.hpp>
|
||||
|
||||
namespace libnest2d { namespace svg {
|
||||
|
||||
class SVGWriter {
|
||||
public:
|
||||
|
||||
enum OrigoLocation {
|
||||
TOPLEFT,
|
||||
BOTTOMLEFT
|
||||
};
|
||||
|
||||
struct Config {
|
||||
OrigoLocation origo_location;
|
||||
Coord mm_in_coord_units;
|
||||
double width, height;
|
||||
Config():
|
||||
origo_location(BOTTOMLEFT), mm_in_coord_units(1000000),
|
||||
width(500), height(500) {}
|
||||
|
||||
};
|
||||
|
||||
private:
|
||||
Config conf_;
|
||||
std::vector<std::string> svg_layers_;
|
||||
bool finished_ = false;
|
||||
public:
|
||||
|
||||
SVGWriter(const Config& conf = Config()):
|
||||
conf_(conf) {}
|
||||
|
||||
void setSize(const Box& box) {
|
||||
conf_.height = static_cast<double>(box.height()) /
|
||||
conf_.mm_in_coord_units;
|
||||
conf_.width = static_cast<double>(box.width()) /
|
||||
conf_.mm_in_coord_units;
|
||||
}
|
||||
|
||||
void writeItem(const Item& item) {
|
||||
if(svg_layers_.empty()) addLayer();
|
||||
Item tsh(item.transformedShape());
|
||||
if(conf_.origo_location == BOTTOMLEFT)
|
||||
for(unsigned i = 0; i < tsh.vertexCount(); i++) {
|
||||
auto v = tsh.vertex(i);
|
||||
setY(v, -getY(v) + conf_.height*conf_.mm_in_coord_units);
|
||||
tsh.setVertex(i, v);
|
||||
}
|
||||
currentLayer() += ShapeLike::serialize<Formats::SVG>(tsh.rawShape(),
|
||||
1.0/conf_.mm_in_coord_units) + "\n";
|
||||
}
|
||||
|
||||
void writePackGroup(const PackGroup& result) {
|
||||
for(auto r : result) {
|
||||
addLayer();
|
||||
for(Item& sh : r) {
|
||||
writeItem(sh);
|
||||
}
|
||||
finishLayer();
|
||||
}
|
||||
}
|
||||
|
||||
void addLayer() {
|
||||
svg_layers_.emplace_back(header());
|
||||
finished_ = false;
|
||||
}
|
||||
|
||||
void finishLayer() {
|
||||
currentLayer() += "\n</svg>\n";
|
||||
finished_ = true;
|
||||
}
|
||||
|
||||
void save(const std::string& filepath) {
|
||||
unsigned lyrc = svg_layers_.size() > 1? 1 : 0;
|
||||
unsigned last = svg_layers_.size() > 1? svg_layers_.size() : 0;
|
||||
|
||||
for(auto& lyr : svg_layers_) {
|
||||
std::fstream out(filepath + (lyrc > 0? std::to_string(lyrc) : "") +
|
||||
".svg", std::fstream::out);
|
||||
if(out.is_open()) out << lyr;
|
||||
if(lyrc == last && !finished_) out << "\n</svg>\n";
|
||||
out.flush(); out.close(); lyrc++;
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::string& currentLayer() { return svg_layers_.back(); }
|
||||
|
||||
const std::string header() const {
|
||||
std::string svg_header =
|
||||
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg height=")raw";
|
||||
svg_header += std::to_string(conf_.height) + "\" width=\"" + std::to_string(conf_.width) + "\" ";
|
||||
svg_header += R"raw(xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">)raw";
|
||||
return svg_header;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SVGTOOLS_HPP
|
@ -1,5 +1,4 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "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) {
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user