Merge branch 'tm_rotcalipers'

This commit is contained in:
tamasmeszaros 2019-06-19 13:11:18 +02:00
commit 1e7b5c5a81
25 changed files with 1329 additions and 898 deletions

View File

@ -48,6 +48,9 @@ set(LIBNEST2D_SRCFILES
${SRC_DIR}/libnest2d/optimizer.hpp
${SRC_DIR}/libnest2d/utils/metaloop.hpp
${SRC_DIR}/libnest2d/utils/rotfinder.hpp
${SRC_DIR}/libnest2d/utils/rotcalipers.hpp
${SRC_DIR}/libnest2d/utils/bigint.hpp
${SRC_DIR}/libnest2d/utils/rational.hpp
${SRC_DIR}/libnest2d/placers/placer_boilerplate.hpp
${SRC_DIR}/libnest2d/placers/bottomleftplacer.hpp
${SRC_DIR}/libnest2d/placers/nfpplacer.hpp
@ -70,12 +73,10 @@ if(TBB_FOUND)
# The Intel TBB library will use the std::exception_ptr feature of C++11.
target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0)
target_link_libraries(libnest2d INTERFACE tbb)
# The following breaks compilation on Visual Studio in Debug mode.
#find_package(Threads REQUIRED)
#target_link_libraries(libnest2d INTERFACE ${TBB_LIBRARIES} ${CMAKE_DL_LIBS}
# Threads::Threads
# )
find_package(Threads REQUIRED)
target_link_libraries(libnest2d INTERFACE ${TBB_LIBRARIES} ${CMAKE_DL_LIBS}
Threads::Threads
)
else()
find_package(OpenMP QUIET)
@ -92,10 +93,11 @@ endif()
add_subdirectory(${SRC_DIR}/libnest2d/backends/${LIBNEST2D_GEOMETRIES})
target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_GEOMETRIES}Backend)
add_subdirectory(${SRC_DIR}/libnest2d/optimizers/${LIBNEST2D_OPTIMIZER})
target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_OPTIMIZER}Optimizer)
#target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES})
# target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES})
target_include_directories(libnest2d INTERFACE ${SRC_DIR})
if(NOT LIBNEST2D_HEADER_ONLY)

View File

@ -47,6 +47,17 @@ using NfpPlacer = _NfpPlacer<Box>;
// This supports only box shaped bins
using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>;
#ifdef LIBNEST2D_STATIC
extern template class Nester<NfpPlacer, FirstFitSelection>;
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
extern template PackGroup Nester<NfpPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
extern template PackGroup Nester<BottomLeftPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator);
#endif
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Iterator = std::vector<Item>::iterator>
@ -60,19 +71,6 @@ PackGroup nest(Iterator from, Iterator to,
return nester.execute(from, to);
}
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Container = std::vector<Item>>
PackGroup nest(Container&& cont,
const typename Placer::BinType& bin,
Coord dist = 0,
const typename Placer::Config& pconf = {},
const typename Selector::Config& sconf = {})
{
return nest<Placer, Selector>(cont.begin(), cont.end(),
bin, dist, pconf, sconf);
}
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Iterator = std::vector<Item>::iterator>
@ -90,6 +88,42 @@ PackGroup nest(Iterator from, Iterator to,
return nester.execute(from, to);
}
#ifdef LIBNEST2D_STATIC
extern template class Nester<NfpPlacer, FirstFitSelection>;
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
extern template PackGroup nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
extern template PackGroup nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
#endif
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Container = std::vector<Item>>
PackGroup nest(Container&& cont,
const typename Placer::BinType& bin,
Coord dist = 0,
const typename Placer::Config& pconf = {},
const typename Selector::Config& sconf = {})
{
return nest<Placer, Selector>(cont.begin(), cont.end(),
bin, dist, pconf, sconf);
}
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Container = std::vector<Item>>
@ -105,71 +139,6 @@ PackGroup nest(Container&& cont,
bin, prg, scond, dist, pconf, sconf);
}
#ifdef LIBNEST2D_STATIC
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>&>(
std::vector<Item>& cont,
const Box& bin,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>&>(
std::vector<Item>& cont,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>>(
std::vector<Item>&& cont,
const Box& bin,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>>(
std::vector<Item>&& cont,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>::iterator>(
std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
extern template
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>::iterator>(
std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist,
const NfpPlacer::Config& pcfg,
const FirstFitSelection::Config& scfg
);
#endif
}
#endif // LIBNEST2D_H

View File

@ -33,19 +33,18 @@ if(NOT TARGET clipper) # If there is a clipper target in the parent project we a
# ${clipper_library_BINARY_DIR}
# )
add_library(ClipperBackend STATIC
add_library(clipperBackend STATIC
${clipper_library_SOURCE_DIR}/clipper.cpp
${clipper_library_SOURCE_DIR}/clipper.hpp)
target_include_directories(ClipperBackend INTERFACE
${clipper_library_SOURCE_DIR})
target_include_directories(clipperBackend INTERFACE ${clipper_library_SOURCE_DIR})
else()
message(FATAL_ERROR "Can't find clipper library and no SVN client found to download.
You can download the clipper sources and define a clipper target in your project, that will work for libnest2d.")
endif()
else()
add_library(ClipperBackend INTERFACE)
target_link_libraries(ClipperBackend INTERFACE Clipper::Clipper)
add_library(clipperBackend INTERFACE)
target_link_libraries(clipperBackend INTERFACE Clipper::Clipper)
endif()
else()
# set(CLIPPER_INCLUDE_DIRS "" PARENT_SCOPE)
@ -69,6 +68,6 @@ target_link_libraries(clipperBackend INTERFACE Boost::boost )
target_compile_definitions(clipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER)
# And finally plug the ClipperBackend into libnest2d
#target_link_libraries(libnest2d INTERFACE ClipperBackend)
# And finally plug the clipperBackend into libnest2d
# target_link_libraries(libnest2d INTERFACE clipperBackend)

View File

@ -12,13 +12,13 @@ struct Polygon {
inline Polygon() = default;
inline explicit Polygon(const Path& cont): Contour(cont) {}
inline explicit Polygon(const Paths& holes):
Holes(holes) {}
// inline explicit Polygon(const Paths& holes):
// Holes(holes) {}
inline Polygon(const Path& cont, const Paths& holes):
Contour(cont), Holes(holes) {}
inline explicit Polygon(Path&& cont): Contour(std::move(cont)) {}
inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {}
// inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {}
inline Polygon(Path&& cont, Paths&& holes):
Contour(std::move(cont)), Holes(std::move(holes)) {}
};
@ -42,7 +42,7 @@ inline IntPoint& operator -=(IntPoint& p, const IntPoint& pa ) {
return p;
}
inline IntPoint operator -(IntPoint& p ) {
inline IntPoint operator -(const IntPoint& p ) {
IntPoint ret = p;
ret.X = -ret.X;
ret.Y = -ret.Y;

View File

@ -20,43 +20,23 @@ using PathImpl = ClipperLib::Path;
using HoleStore = ClipperLib::Paths;
using PolygonImpl = ClipperLib::Polygon;
// Type of coordinate units used by Clipper
template<> struct CoordType<PointImpl> {
using Type = ClipperLib::cInt;
};
// Type of point used by Clipper
template<> struct PointType<PolygonImpl> {
using Type = PointImpl;
};
template<> struct PointType<PathImpl> {
using Type = PointImpl;
};
template<> struct PointType<PointImpl> {
using Type = PointImpl;
};
template<> struct CountourType<PolygonImpl> {
using Type = PathImpl;
};
template<> struct ShapeTag<PolygonImpl> { using Type = PolygonTag; };
template<> struct ShapeTag<PathImpl> { using Type = PathTag; };
template<> struct ShapeTag<PointImpl> { using Type = PointTag; };
template<> struct ShapeTag<TMultiShape<PolygonImpl>> {
using Type = MultiPolygonTag;
};
// Type of coordinate units used by Clipper. Enough to specialize for point,
// the rest of the types will work (Path, Polygon)
template<> struct CoordType<PointImpl> { using Type = ClipperLib::cInt; };
template<> struct PointType<TMultiShape<PolygonImpl>> {
using Type = PointImpl;
};
// Enough to specialize for path, it will work for multishape and Polygon
template<> struct PointType<PathImpl> { using Type = PointImpl; };
template<> struct HolesContainer<PolygonImpl> {
using Type = ClipperLib::Paths;
};
// This is crucial. CountourType refers to itself by default, so we don't have
// to secialize for clipper Path. ContourType<PathImpl>::Type is PathImpl.
template<> struct ContourType<PolygonImpl> { using Type = PathImpl; };
// The holes are contained in Clipper::Paths
template<> struct HolesContainer<PolygonImpl> { using Type = ClipperLib::Paths; };
namespace pointlike {
@ -86,39 +66,11 @@ template<> inline TCoord<PointImpl>& y(PointImpl& p)
}
// Using the libnest2d default area implementation
#define DISABLE_BOOST_AREA
namespace _smartarea {
template<Orientation o>
inline double area(const PolygonImpl& /*sh*/) {
return std::nan("");
}
template<>
inline double area<Orientation::COUNTER_CLOCKWISE>(const PolygonImpl& sh) {
return std::accumulate(sh.Holes.begin(), sh.Holes.end(),
ClipperLib::Area(sh.Contour),
[](double a, const ClipperLib::Path& pt){
return a + ClipperLib::Area(pt);
});
}
template<>
inline double area<Orientation::CLOCKWISE>(const PolygonImpl& sh) {
return -area<Orientation::COUNTER_CLOCKWISE>(sh);
}
}
namespace shapelike {
// Tell libnest2d how to make string out of a ClipperPolygon object
template<> inline double area(const PolygonImpl& sh, const PolygonTag&)
{
return _smartarea::area<OrientationType<PolygonImpl>::Value>(sh);
}
template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance)
{
#define DISABLE_BOOST_OFFSET
@ -200,20 +152,7 @@ inline PolygonImpl create(const PathImpl& path, const HoleStore& holes)
{
PolygonImpl p;
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);
}
p.Holes = holes;
for(auto& h : p.Holes) {
if(!ClipperLib::Orientation(h)) {
ClipperLib::ReversePath(h);
}
}
return p;
}
@ -221,22 +160,8 @@ inline PolygonImpl create(const PathImpl& path, const HoleStore& holes)
template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) {
PolygonImpl p;
p.Contour.swap(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);
}
p.Holes.swap(holes);
for(auto& h : p.Holes) {
if(!ClipperLib::Orientation(h)) {
ClipperLib::ReversePath(h);
}
}
return p;
}
@ -314,13 +239,13 @@ inline void rotate(PolygonImpl& sh, const Radians& rads)
} // namespace shapelike
#define DISABLE_BOOST_NFP_MERGE
inline std::vector<PolygonImpl> clipper_execute(
inline TMultiShape<PolygonImpl> clipper_execute(
ClipperLib::Clipper& clipper,
ClipperLib::ClipType clipType,
ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd,
ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd)
{
shapelike::Shapes<PolygonImpl> retv;
TMultiShape<PolygonImpl> retv;
ClipperLib::PolyTree result;
clipper.Execute(clipType, result, subjFillType, clipFillType);
@ -370,8 +295,8 @@ inline std::vector<PolygonImpl> clipper_execute(
namespace nfp {
template<> inline std::vector<PolygonImpl>
merge(const std::vector<PolygonImpl>& shapes)
template<> inline TMultiShape<PolygonImpl>
merge(const TMultiShape<PolygonImpl>& shapes)
{
ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);
@ -394,6 +319,8 @@ merge(const std::vector<PolygonImpl>& shapes)
}
#define DISABLE_BOOST_CONVEX_HULL
//#define DISABLE_BOOST_SERIALIZE
//#define DISABLE_BOOST_UNSERIALIZE

View File

@ -9,6 +9,7 @@
#include <string>
#include <cmath>
#include <type_traits>
#include <limits>
#if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L
#define BP2D_NOEXCEPT
@ -197,6 +198,33 @@ public:
}
};
struct ScalarTag {};
struct BigIntTag {};
struct RationalTag {};
template<class T> struct _NumTag {
using Type =
enable_if_t<std::is_arithmetic<T>::value, ScalarTag>;
};
template<class T> using NumTag = typename _NumTag<remove_cvref_t<T>>::Type;
/// A local version for abs that is garanteed to work with libnest2d types
template <class T> inline T abs(const T& v, ScalarTag)
{
return std::abs(v);
}
template<class T> inline T abs(const T& v) { return abs(v, NumTag<T>()); }
template<class T2, class T1> inline T2 cast(const T1& v, ScalarTag, ScalarTag)
{
return static_cast<T2>(v);
}
template<class T2, class T1> inline T2 cast(const T1& v) {
return cast<T2, T1>(v, NumTag<T1>(), NumTag<T2>());
}
}
#endif // LIBNEST2D_CONFIG_HPP

View File

@ -7,45 +7,125 @@
#include <array>
#include <vector>
#include <numeric>
#include <limits>
#include <iterator>
#include <cmath>
#include <cstdint>
#include "common.hpp"
#include <libnest2d/common.hpp>
namespace libnest2d {
// Meta tags for different geometry concepts.
struct PointTag {};
struct PolygonTag {};
struct PathTag {};
struct MultiPolygonTag {};
struct BoxTag {};
struct CircleTag {};
/// Meta-function to derive the tag of a shape type.
template<class Shape> struct ShapeTag { using Type = typename Shape::Tag; };
/// Tag<S> will be used instead of `typename ShapeTag<S>::Type`
template<class S> using Tag = typename ShapeTag<remove_cvref_t<S>>::Type;
/// Meta function to derive the contour type for a polygon which could be itself
template<class RawShape> struct ContourType { using Type = RawShape; };
/// TContour<RawShape> instead of `typename ContourType<RawShape>::type`
template<class RawShape>
using TContour = typename ContourType<remove_cvref_t<RawShape>>::Type;
/// Getting the type of point structure used by a shape.
template<class Sh> struct PointType {
using Type = typename PointType<TContour<Sh>>::Type;
};
/// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
template<class Shape>
using TPoint = typename PointType<remove_cvref_t<Shape>>::Type;
/// Getting the coordinate data type for a geometry class.
template<class GeomClass> struct CoordType { using Type = long; };
template<class GeomClass> struct CoordType {
using Type = typename CoordType<TPoint<GeomClass>>::Type;
};
/// TCoord<GeomType> as shorthand for typename `CoordType<GeomType>::Type`.
template<class GeomType>
using TCoord = typename CoordType<remove_cvref_t<GeomType>>::Type;
/// Getting the type of point structure used by a shape.
template<class Sh> struct PointType { using Type = typename Sh::PointType; };
/// Getting the computation type for a certain geometry type.
/// It is the coordinate type by default but it is advised that a type with
/// larger precision and (or) range is specified.
template<class T, bool = std::is_arithmetic<T>::value> struct ComputeType {};
/// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
template<class Shape>
using TPoint = typename PointType<remove_cvref_t<Shape>>::Type;
/// A compute type is introduced to hold the results of computations on
/// coordinates and points. It should be larger in range than the coordinate
/// type or the range of coordinates should be limited to not loose precision.
template<class GeomClass> struct ComputeType<GeomClass, false> {
using Type = typename ComputeType<TCoord<GeomClass>>::Type;
};
/// libnest2d will choose a default compute type for various coordinate types
/// if the backend has not specified anything.
template<class T> struct DoublePrecision { using Type = T; };
template<> struct DoublePrecision<int8_t> { using Type = int16_t; };
template<> struct DoublePrecision<int16_t> { using Type = int32_t; };
template<> struct DoublePrecision<int32_t> { using Type = int64_t; };
template<> struct DoublePrecision<float> { using Type = double; };
template<> struct DoublePrecision<double> { using Type = long double; };
template<class I> struct ComputeType<I, true> {
using Type = typename DoublePrecision<I>::Type;
};
template<class RawShape> struct CountourType { using Type = RawShape; };
template<class RawShape>
using TContour = typename CountourType<remove_cvref_t<RawShape>>::Type;
/// TCompute<T> shorthand for `typename ComputeType<T>::Type`
template<class T> using TCompute = typename ComputeType<remove_cvref_t<T>>::Type;
/// A meta function to derive a container type for holes in a polygon
template<class RawShape>
struct HolesContainer { using Type = std::vector<TContour<RawShape>>; };
/// Shorthand for `typename HolesContainer<RawShape>::Type`
template<class RawShape>
using THolesContainer = typename HolesContainer<remove_cvref_t<RawShape>>::Type;
/*
* TContour, TPoint, TCoord and TCompute should be usable for any type for which
* it makes sense. For example, the point type could be derived from the contour,
* the polygon and (or) the multishape as well. The coordinate type also and
* including the point type. TCoord<Polygon>, TCoord<Path>, TCoord<Point> are
* all valid types and derives the coordinate type of template argument Polygon,
* Path and Point. This is also true for TCompute, but it can also take the
* coordinate type as argument.
*/
template<class RawShape>
struct LastPointIsFirst { static const bool Value = true; };
/*
* A Multi shape concept is also introduced. A multi shape is something that
* can contain the result of an operation where the input is one polygon and
* the result could be many polygons or path -> paths. The MultiShape should be
* a container type. If the backend does not specialize the MultiShape template,
* a default multi shape container will be used.
*/
/// The default multi shape container.
template<class S> struct DefaultMultiShape: public std::vector<S> {
using Tag = MultiPolygonTag;
template<class...Args> DefaultMultiShape(Args&&...args):
std::vector<S>(std::forward<Args>(args)...) {}
};
/// The MultiShape Type trait which gets the container type for a geometry type.
template<class S> struct MultiShape { using Type = DefaultMultiShape<S>; };
/// use TMultiShape<S> instead of `typename MultiShape<S>::Type`
template<class S>
using TMultiShape = typename MultiShape<remove_cvref_t<S>>::Type;
// A specialization of ContourType to work with the default multishape type
template<class S> struct ContourType<DefaultMultiShape<S>> {
using Type = typename ContourType<S>::Type;
};
enum class Orientation {
CLOCKWISE,
@ -59,6 +139,11 @@ struct OrientationType {
static const Orientation Value = Orientation::CLOCKWISE;
};
template<class T> inline /*constexpr*/ bool is_clockwise() {
return OrientationType<TContour<T>>::Value == Orientation::CLOCKWISE;
}
/**
* \brief A point pair base class for other point pairs (segment, box, ...).
* \tparam RawPoint The actual point type to use.
@ -69,21 +154,6 @@ struct PointPair {
RawPoint p2;
};
struct PointTag {};
struct PolygonTag {};
struct PathTag {};
struct MultiPolygonTag {};
struct BoxTag {};
struct CircleTag {};
/// Meta-functions to derive the tags
template<class Shape> struct ShapeTag { using Type = typename Shape::Tag; };
template<class S> using Tag = typename ShapeTag<remove_cvref_t<S>>::Type;
template<class S> struct MultiShape { using Type = std::vector<S>; };
template<class S>
using TMultiShape =typename MultiShape<remove_cvref_t<S>>::Type;
/**
* \brief An abstraction of a box;
*/
@ -114,11 +184,16 @@ public:
inline RawPoint center() const BP2D_NOEXCEPT;
inline double area() const BP2D_NOEXCEPT {
return double(width()*height());
template<class Unit = TCompute<RawPoint>>
inline Unit area() const BP2D_NOEXCEPT {
return Unit(width())*height();
}
};
template<class S> struct PointType<_Box<S>> {
using Type = typename _Box<S>::PointType;
};
template<class RawPoint>
class _Circle {
RawPoint center_;
@ -129,7 +204,6 @@ public:
using PointType = RawPoint;
_Circle() = default;
_Circle(const RawPoint& center, double r): center_(center), radius_(r) {}
inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; }
@ -139,10 +213,14 @@ public:
inline void radius(double r) { radius_ = r; }
inline double area() const BP2D_NOEXCEPT {
return 2.0*Pi*radius_*radius_;
return Pi_2 * radius_ * radius_;
}
};
template<class S> struct PointType<_Circle<S>> {
using Type = typename _Circle<S>::PointType;
};
/**
* \brief An abstraction of a directed line segment with two points.
*/
@ -185,7 +263,12 @@ public:
inline Radians angleToXaxis() const;
/// The length of the segment in the measure of the coordinate system.
inline double length();
template<class Unit = TCompute<RawPoint>> inline Unit sqlength() const;
};
template<class S> struct PointType<_Segment<S>> {
using Type = typename _Circle<S>::PointType;
};
// This struct serves almost as a namespace. The only difference is that is can
@ -216,33 +299,56 @@ inline TCoord<RawPoint>& y(RawPoint& p)
return p.y();
}
template<class RawPoint>
inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/)
template<class RawPoint, class Unit = TCompute<RawPoint>>
inline Unit squaredDistance(const RawPoint& p1, const RawPoint& p2)
{
static_assert(always_false<RawPoint>::value,
"PointLike::distance(point, point) unimplemented!");
return 0;
auto x1 = Unit(x(p1)), y1 = Unit(y(p1)), x2 = Unit(x(p2)), y2 = Unit(y(p2));
Unit a = (x2 - x1), b = (y2 - y1);
return a * a + b * b;
}
template<class RawPoint>
inline double distance(const RawPoint& /*p1*/,
const _Segment<RawPoint>& /*s*/)
inline double distance(const RawPoint& p1, const RawPoint& p2)
{
static_assert(always_false<RawPoint>::value,
"PointLike::distance(point, segment) unimplemented!");
return 0;
return std::sqrt(squaredDistance<RawPoint, double>(p1, p2));
}
template<class RawPoint>
inline std::pair<TCoord<RawPoint>, bool> horizontalDistance(
// create perpendicular vector
template<class Pt> inline Pt perp(const Pt& p)
{
return Pt(y(p), -x(p));
}
template<class Pt, class Unit = TCompute<Pt>>
inline Unit dotperp(const Pt& a, const Pt& b)
{
return Unit(x(a)) * Unit(y(b)) - Unit(y(a)) * Unit(x(b));
}
// dot product
template<class Pt, class Unit = TCompute<Pt>>
inline Unit dot(const Pt& a, const Pt& b)
{
return Unit(x(a)) * x(b) + Unit(y(a)) * y(b);
}
// squared vector magnitude
template<class Pt, class Unit = TCompute<Pt>>
inline Unit magnsq(const Pt& p)
{
return Unit(x(p)) * x(p) + Unit(y(p)) * y(p);
}
template<class RawPoint, class Unit = TCompute<RawPoint>>
inline std::pair<Unit, bool> horizontalDistance(
const RawPoint& p, const _Segment<RawPoint>& s)
{
using Unit = TCoord<RawPoint>;
auto x = pointlike::x(p), y = pointlike::y(p);
auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first());
auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second());
namespace pl = pointlike;
auto x = Unit(pl::x(p)), y = Unit(pl::y(p));
auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first()));
auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second()));
TCoord<RawPoint> ret;
Unit ret;
if( (y < y1 && y < y2) || (y > y1 && y > y2) )
return {0, false};
@ -250,8 +356,7 @@ inline std::pair<TCoord<RawPoint>, bool> horizontalDistance(
ret = std::min( x-x1, x -x2);
else if( (y == y1 && y == y2) && (x < x1 && x < x2))
ret = -std::min(x1 - x, x2 - x);
else if(std::abs(y - y1) <= std::numeric_limits<Unit>::epsilon() &&
std::abs(y - y2) <= std::numeric_limits<Unit>::epsilon())
else if(y == y1 && y == y2)
ret = 0;
else
ret = x - x1 + (x1 - x2)*(y1 - y)/(y1 - y2);
@ -259,16 +364,16 @@ inline std::pair<TCoord<RawPoint>, bool> horizontalDistance(
return {ret, true};
}
template<class RawPoint>
inline std::pair<TCoord<RawPoint>, bool> verticalDistance(
template<class RawPoint, class Unit = TCompute<RawPoint>>
inline std::pair<Unit, bool> verticalDistance(
const RawPoint& p, const _Segment<RawPoint>& s)
{
using Unit = TCoord<RawPoint>;
auto x = pointlike::x(p), y = pointlike::y(p);
auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first());
auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second());
namespace pl = pointlike;
auto x = Unit(pl::x(p)), y = Unit(pl::y(p));
auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first()));
auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second()));
TCoord<RawPoint> ret;
Unit ret;
if( (x < x1 && x < x2) || (x > x1 && x > x2) )
return {0, false};
@ -276,8 +381,7 @@ inline std::pair<TCoord<RawPoint>, bool> verticalDistance(
ret = std::min( y-y1, y -y2);
else if( (x == x1 && x == x2) && (y < y1 && y < y2))
ret = -std::min(y1 - y, y2 - y);
else if(std::abs(x - x1) <= std::numeric_limits<Unit>::epsilon() &&
std::abs(x - x2) <= std::numeric_limits<Unit>::epsilon())
else if(x == x1 && x == x2)
ret = 0;
else
ret = y - y1 + (y1 - y2)*(x1 - x)/(x1 - x2);
@ -333,9 +437,10 @@ inline Radians _Segment<RawPoint>::angleToXaxis() const
}
template<class RawPoint>
inline double _Segment<RawPoint>::length()
template<class Unit>
inline Unit _Segment<RawPoint>::sqlength() const
{
return pointlike::distance(first(), second());
return pointlike::squaredDistance<RawPoint, Unit>(first(), second());
}
template<class RawPoint>
@ -346,8 +451,8 @@ inline RawPoint _Box<RawPoint>::center() const BP2D_NOEXCEPT {
using Coord = TCoord<RawPoint>;
RawPoint ret = { // No rounding here, we dont know if these are int coords
static_cast<Coord>( (getX(minc) + getX(maxc))/2.0 ),
static_cast<Coord>( (getY(minc) + getY(maxc))/2.0 )
Coord( (getX(minc) + getX(maxc)) / Coord(2) ),
Coord( (getY(minc) + getY(maxc)) / Coord(2) )
};
return ret;
@ -362,9 +467,6 @@ enum class Formats {
// used in friend declarations and can be aliased at class scope.
namespace shapelike {
template<class RawShape>
using Shapes = TMultiShape<RawShape>;
template<class RawShape>
inline RawShape create(const TContour<RawShape>& contour,
const THolesContainer<RawShape>& holes)
@ -449,7 +551,7 @@ inline void reserve(RawPath& p, size_t vertex_capacity, const PathTag&)
template<class RawShape, class...Args>
inline void addVertex(RawShape& sh, const PathTag&, Args...args)
{
return sh.emplace_back(std::forward<Args>(args)...);
sh.emplace_back(std::forward<Args>(args)...);
}
template<class RawShape, class Fn>
@ -504,13 +606,8 @@ inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/)
"shapelike::unserialize() unimplemented!");
}
template<class RawShape>
inline double area(const RawShape& /*sh*/, const PolygonTag&)
{
static_assert(always_false<RawShape>::value,
"shapelike::area() unimplemented!");
return 0;
}
template<class Cntr, class Unit = double>
inline Unit area(const Cntr& poly, const PathTag& );
template<class RawShape>
inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/)
@ -556,14 +653,14 @@ inline bool touches( const TPoint<RawShape>& /*point*/,
template<class RawShape>
inline _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/,
const PolygonTag&)
const PathTag&)
{
static_assert(always_false<RawShape>::value,
"shapelike::boundingBox(shape) unimplemented!");
}
template<class RawShapes>
inline _Box<TPoint<typename RawShapes::value_type>>
inline _Box<TPoint<RawShapes>>
boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&)
{
static_assert(always_false<RawShapes>::value,
@ -571,21 +668,10 @@ boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&)
}
template<class RawShape>
inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&)
{
static_assert(always_false<RawShape>::value,
"shapelike::convexHull(shape) unimplemented!");
return RawShape();
}
inline RawShape convexHull(const RawShape& sh, const PathTag&);
template<class RawShapes>
inline typename RawShapes::value_type
convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&)
{
static_assert(always_false<RawShapes>::value,
"shapelike::convexHull(shapes) unimplemented!");
return typename RawShapes::value_type();
}
template<class RawShapes, class S = typename RawShapes::value_type>
inline S convexHull(const RawShapes& sh, const MultiPolygonTag&);
template<class RawShape>
inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
@ -745,13 +831,19 @@ inline void reserve(T& sh, size_t vertex_capacity) {
template<class RawShape, class...Args>
inline void addVertex(RawShape& sh, const PolygonTag&, Args...args)
{
return addVertex(contour(sh), PathTag(), std::forward<Args>(args)...);
addVertex(contour(sh), PathTag(), std::forward<Args>(args)...);
}
template<class RawShape, class...Args> // Tag dispatcher
inline void addVertex(RawShape& sh, Args...args)
{
return addVertex(sh, Tag<RawShape>(), std::forward<Args>(args)...);
addVertex(sh, Tag<RawShape>(), std::forward<Args>(args)...);
}
template<class RawShape>
inline _Box<TPoint<RawShape>> boundingBox(const RawShape& poly, const PolygonTag&)
{
return boundingBox(contour(poly), PathTag());
}
template<class Box>
@ -786,7 +878,7 @@ inline _Box<TPoint<S>> boundingBox(const S& sh)
template<class Box>
inline double area(const Box& box, const BoxTag& )
{
return box.area();
return box.template area<double>();
}
template<class Circle>
@ -795,6 +887,35 @@ inline double area(const Circle& circ, const CircleTag& )
return circ.area();
}
template<class Cntr, class Unit>
inline Unit area(const Cntr& poly, const PathTag& )
{
namespace sl = shapelike;
if (sl::cend(poly) - sl::cbegin(poly) < 3) return 0.0;
Unit a = 0;
for (auto i = sl::cbegin(poly), j = std::prev(sl::cend(poly));
i < sl::cend(poly); ++i)
{
auto xj = Unit(getX(*j)), yj = Unit(getY(*j));
auto xi = Unit(getX(*i)), yi = Unit(getY(*i));
a += (xj + xi) * (yj - yi);
j = i;
}
a /= 2;
return is_clockwise<Cntr>() ? a : -a;
}
template<class S> inline double area(const S& poly, const PolygonTag& )
{
auto hls = holes(poly);
return std::accumulate(hls.begin(), hls.end(),
area(contour(poly), PathTag()),
[](double a, const TContour<S> &h){
return a + area(h, PathTag());
});
}
template<class RawShape> // Dispatching function
inline double area(const RawShape& sh)
{
@ -811,6 +932,12 @@ inline double area(const RawShapes& shapes, const MultiPolygonTag&)
});
}
template<class RawShape>
inline RawShape convexHull(const RawShape& sh, const PolygonTag&)
{
return create<RawShape>(convexHull(contour(sh), PathTag()));
}
template<class RawShape>
inline auto convexHull(const RawShape& sh)
-> decltype(convexHull(sh, Tag<RawShape>())) // TODO: C++14 could deduce
@ -818,11 +945,91 @@ inline auto convexHull(const RawShape& sh)
return convexHull(sh, Tag<RawShape>());
}
template<class RawShape>
inline RawShape convexHull(const RawShape& sh, const PathTag&)
{
using Unit = TCompute<RawShape>;
using Point = TPoint<RawShape>;
namespace sl = shapelike;
size_t edges = sl::cend(sh) - sl::cbegin(sh);
if(edges <= 3) return {};
bool closed = false;
std::vector<Point> U, L;
U.reserve(1 + edges / 2); L.reserve(1 + edges / 2);
std::vector<Point> pts; pts.reserve(edges);
std::copy(sl::cbegin(sh), sl::cend(sh), std::back_inserter(pts));
auto fpt = pts.front(), lpt = pts.back();
if(getX(fpt) == getX(lpt) && getY(fpt) == getY(lpt)) {
closed = true; pts.pop_back();
}
std::sort(pts.begin(), pts.end(),
[](const Point& v1, const Point& v2)
{
Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2);
return x1 == x2 ? y1 < y2 : x1 < x2;
});
auto dir = [](const Point& p, const Point& q, const Point& r) {
return (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) -
(Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p));
};
auto ik = pts.begin();
while(ik != pts.end()) {
while(U.size() > 1 && dir(U[U.size() - 2], U.back(), *ik) <= 0)
U.pop_back();
while(L.size() > 1 && dir(L[L.size() - 2], L.back(), *ik) >= 0)
L.pop_back();
U.emplace_back(*ik);
L.emplace_back(*ik);
++ik;
}
RawShape ret; reserve(ret, U.size() + L.size());
if(is_clockwise<RawShape>()) {
for(auto it = U.begin(); it != std::prev(U.end()); ++it)
addVertex(ret, *it);
for(auto it = L.rbegin(); it != std::prev(L.rend()); ++it)
addVertex(ret, *it);
if(closed) addVertex(ret, *std::prev(L.rend()));
} else {
for(auto it = L.begin(); it != std::prev(L.end()); ++it)
addVertex(ret, *it);
for(auto it = U.rbegin(); it != std::prev(U.rend()); ++it)
addVertex(ret, *it);
if(closed) addVertex(ret, *std::prev(U.rend()));
}
return ret;
}
template<class RawShapes, class S>
inline S convexHull(const RawShapes& sh, const MultiPolygonTag&)
{
namespace sl = shapelike;
S cntr;
for(auto& poly : sh)
for(auto it = sl::cbegin(poly); it != sl::cend(poly); ++it)
addVertex(cntr, *it);
return convexHull(cntr, Tag<S>());
}
template<class TP, class TC>
inline bool isInside(const TP& point, const TC& circ,
const PointTag&, const CircleTag&)
{
return pointlike::distance(point, circ.center()) < circ.radius();
auto r = circ.radius();
return pointlike::squaredDistance(point, circ.center()) < r * r;
}
template<class TP, class TB>
@ -972,6 +1179,9 @@ template<class RawShape> inline bool isConvex(const RawShape& sh) // dispatch
using Segment = _Segment<Point>; \
using Polygons = TMultiShape<T>
namespace sl = shapelike;
namespace pl = pointlike;
}
#endif // GEOMETRY_TRAITS_HPP

View File

@ -1,26 +1,22 @@
#ifndef GEOMETRIES_NOFITPOLYGON_HPP
#define GEOMETRIES_NOFITPOLYGON_HPP
#include "geometry_traits.hpp"
#include <algorithm>
#include <functional>
#include <vector>
#include <iterator>
#include <libnest2d/geometry_traits.hpp>
namespace libnest2d {
namespace __nfp {
// Do not specialize this...
template<class RawShape>
template<class RawShape, class Unit = TCompute<RawShape>>
inline bool _vsort(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2)
{
using Coord = TCoord<TPoint<RawShape>>;
Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2);
auto diff = y1 - y2;
if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
return x1 < x2;
return diff < 0;
Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2);
return y1 == y2 ? x1 < x2 : y1 < y2;
}
template<class EdgeList, class RawShape, class Vertex = TPoint<RawShape>>
@ -202,7 +198,7 @@ inline TPoint<RawShape> referenceVertex(const RawShape& sh)
* convex as well in this case.
*
*/
template<class RawShape>
template<class RawShape, class Ratio = double>
inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
const RawShape& other)
{
@ -239,11 +235,61 @@ inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
}
}
// 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();
Vertex ax(1, 0); // Unit vector for the X axis
// get cectors from the edges
Vertex p1 = e1.second() - e1.first();
Vertex p2 = e2.second() - e2.first();
// Quadrant mapping array. The quadrant of a vector can be determined
// from the dot product of the vector and its perpendicular pair
// with the unit vector X axis. The products will carry the values
// lcos = dot(p, ax) = l * cos(phi) and
// lsin = -dotperp(p, ax) = l * sin(phi) where
// l is the length of vector p. From the signs of these values we can
// construct an index which has the sign of lcos as MSB and the
// sign of lsin as LSB. This index can be used to retrieve the actual
// quadrant where vector p resides using the following map:
// (+ is 0, - is 1)
// cos | sin | decimal | quadrant
// + | + | 0 | 0
// + | - | 1 | 3
// - | + | 2 | 1
// - | - | 3 | 2
std::array<int, 4> quadrants {0, 3, 1, 2 };
std::array<int, 2> q {0, 0}; // Quadrant indices for p1 and p2
using TDots = std::array<TCompute<Vertex>, 2>;
TDots lcos { pl::dot(p1, ax), pl::dot(p2, ax) };
TDots lsin { -pl::dotperp(p1, ax), -pl::dotperp(p2, ax) };
// Construct the quadrant indices for p1 and p2
for(size_t i = 0; i < 2; ++i)
if(lcos[i] == 0) q[i] = lsin[i] > 0 ? 1 : 3;
else if(lsin[i] == 0) q[i] = lcos[i] > 0 ? 0 : 2;
else q[i] = quadrants[((lcos[i] < 0) << 1) + (lsin[i] < 0)];
if(q[0] == q[1]) { // only bother if p1 and p2 are in the same quadrant
auto lsq1 = pl::magnsq(p1); // squared magnitudes, avoid sqrt
auto lsq2 = pl::magnsq(p2); // squared magnitudes, avoid sqrt
// We will actually compare l^2 * cos^2(phi) which saturates the
// cos function. But with the quadrant info we can get the sign back
int sign = q[0] == 1 || q[0] == 2 ? -1 : 1;
// If Ratio is an actual rational type, there is no precision loss
auto pcos1 = Ratio(lcos[0]) / lsq1 * sign * lcos[0];
auto pcos2 = Ratio(lcos[1]) / lsq2 * sign * lcos[1];
return q[0] < 2 ? pcos1 < pcos2 : pcos1 > pcos2;
}
// If in different quadrants, compare the quadrant indices only.
return q[0] > q[1];
});
__nfp::buildPolygon(edgelist, rsh, top_nfp);
@ -255,454 +301,7 @@ template<class RawShape>
NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
const RawShape& cother)
{
// Algorithms are from the original algorithm proposed in paper:
// https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf
// /////////////////////////////////////////////////////////////////////////
// Algorithm 1: Obtaining the minkowski sum
// /////////////////////////////////////////////////////////////////////////
// I guess this is not a full minkowski sum of the two input polygons by
// definition. This yields a subset that is compatible with the next 2
// algorithms.
using Result = NfpResult<RawShape>;
using Vertex = TPoint<RawShape>;
using Coord = TCoord<Vertex>;
using Edge = _Segment<Vertex>;
namespace sl = shapelike;
using std::signbit;
using std::sort;
using std::vector;
using std::ref;
using std::reference_wrapper;
// TODO The original algorithms expects the stationary polygon in
// counter clockwise and the orbiter in clockwise order.
// So for preventing any further complication, I will make the input
// the way it should be, than make my way around the orientations.
// Reverse the stationary contour to counter clockwise
auto stcont = sl::contour(cstationary);
{
std::reverse(sl::begin(stcont), sl::end(stcont));
stcont.pop_back();
auto it = std::min_element(sl::begin(stcont), sl::end(stcont),
[](const Vertex& v1, const Vertex& v2) {
return getY(v1) < getY(v2);
});
std::rotate(sl::begin(stcont), it, sl::end(stcont));
sl::addVertex(stcont, sl::front(stcont));
}
RawShape stationary;
sl::contour(stationary) = stcont;
// Reverse the orbiter contour to counter clockwise
auto orbcont = sl::contour(cother);
{
std::reverse(orbcont.begin(), orbcont.end());
// Step 1: Make the orbiter reverse oriented
orbcont.pop_back();
auto it = std::min_element(orbcont.begin(), orbcont.end(),
[](const Vertex& v1, const Vertex& v2) {
return getY(v1) < getY(v2);
});
std::rotate(orbcont.begin(), it, orbcont.end());
orbcont.emplace_back(orbcont.front());
for(auto &v : orbcont) v = -v;
}
// Copy the orbiter (contour only), we will have to work on it
RawShape orbiter;
sl::contour(orbiter) = orbcont;
// An edge with additional data for marking it
struct MarkedEdge {
Edge e; Radians turn_angle = 0; bool is_turning_point = false;
MarkedEdge() = default;
MarkedEdge(const Edge& ed, Radians ta, bool tp):
e(ed), turn_angle(ta), is_turning_point(tp) {}
// debug
std::string label;
};
// Container for marked edges
using EdgeList = vector<MarkedEdge>;
EdgeList A, B;
// This is how an edge list is created from the polygons
auto fillEdgeList = [](EdgeList& L, const RawShape& ppoly, int dir) {
auto& poly = sl::contour(ppoly);
L.reserve(sl::contourVertexCount(poly));
if(dir > 0) {
auto it = poly.begin();
auto nextit = std::next(it);
double turn_angle = 0;
bool is_turn_point = false;
while(nextit != poly.end()) {
L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
it++; nextit++;
}
} else {
auto it = sl::rbegin(poly);
auto nextit = std::next(it);
double turn_angle = 0;
bool is_turn_point = false;
while(nextit != sl::rend(poly)) {
L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
it++; nextit++;
}
}
auto getTurnAngle = [](const Edge& e1, const Edge& e2) {
auto phi = e1.angleToXaxis();
auto phi_prev = e2.angleToXaxis();
auto turn_angle = phi-phi_prev;
if(turn_angle > Pi) turn_angle -= TwoPi;
if(turn_angle < -Pi) turn_angle += TwoPi;
return turn_angle;
};
auto eit = L.begin();
auto enext = std::next(eit);
eit->turn_angle = getTurnAngle(L.front().e, L.back().e);
while(enext != L.end()) {
enext->turn_angle = getTurnAngle( enext->e, eit->e);
eit->is_turning_point =
signbit(enext->turn_angle) != signbit(eit->turn_angle);
++eit; ++enext;
}
L.back().is_turning_point = signbit(L.back().turn_angle) !=
signbit(L.front().turn_angle);
};
// Step 2: Fill the edgelists
fillEdgeList(A, stationary, 1);
fillEdgeList(B, orbiter, 1);
int i = 1;
for(MarkedEdge& me : A) {
std::cout << "a" << i << ":\n\t"
<< getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
<< getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
<< "Turning point: " << (me.is_turning_point ? "yes" : "no")
<< std::endl;
me.label = "a"; me.label += std::to_string(i);
i++;
}
i = 1;
for(MarkedEdge& me : B) {
std::cout << "b" << i << ":\n\t"
<< getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
<< getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
<< "Turning point: " << (me.is_turning_point ? "yes" : "no")
<< std::endl;
me.label = "b"; me.label += std::to_string(i);
i++;
}
// A reference to a marked edge that also knows its container
struct MarkedEdgeRef {
reference_wrapper<MarkedEdge> eref;
reference_wrapper<vector<MarkedEdgeRef>> container;
Coord dir = 1; // Direction modifier
inline Radians angleX() const { return eref.get().e.angleToXaxis(); }
inline const Edge& edge() const { return eref.get().e; }
inline Edge& edge() { return eref.get().e; }
inline bool isTurningPoint() const {
return eref.get().is_turning_point;
}
inline bool isFrom(const vector<MarkedEdgeRef>& cont ) {
return &(container.get()) == &cont;
}
inline bool eq(const MarkedEdgeRef& mr) {
return &(eref.get()) == &(mr.eref.get());
}
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
reference_wrapper<vector<MarkedEdgeRef>> ec):
eref(er), container(ec), dir(1) {}
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
reference_wrapper<vector<MarkedEdgeRef>> ec,
Coord d):
eref(er), container(ec), dir(d) {}
};
using EdgeRefList = vector<MarkedEdgeRef>;
// Comparing two marked edges
auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) {
return e1.angleX() < e2.angleX();
};
EdgeRefList Aref, Bref; // We create containers for the references
Aref.reserve(A.size()); Bref.reserve(B.size());
// Fill reference container for the stationary polygon
std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) {
Aref.emplace_back( ref(me), ref(Aref) );
});
// Fill reference container for the orbiting polygon
std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) {
Bref.emplace_back( ref(me), ref(Bref) );
});
auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure
(const EdgeRefList& Q, const EdgeRefList& R, bool positive)
{
// Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)"
// Sort the containers of edge references and merge them.
// Q could be sorted only once and be reused here but we would still
// need to merge it with sorted(R).
EdgeRefList merged;
EdgeRefList S, seq;
merged.reserve(Q.size() + R.size());
merged.insert(merged.end(), R.begin(), R.end());
std::stable_sort(merged.begin(), merged.end(), sortfn);
merged.insert(merged.end(), Q.begin(), Q.end());
std::stable_sort(merged.begin(), merged.end(), sortfn);
// Step 2 "set i = 1, k = 1, direction = 1, s1 = q1"
// we don't use i, instead, q is an iterator into Q. k would be an index
// into the merged sequence but we use "it" as an iterator for that
// here we obtain references for the containers for later comparisons
const auto& Rcont = R.begin()->container.get();
const auto& Qcont = Q.begin()->container.get();
// Set the initial direction
Coord dir = 1;
// roughly i = 1 (so q = Q.begin()) and s1 = q1 so S[0] = q;
if(positive) {
auto q = Q.begin();
S.emplace_back(*q);
// Roughly step 3
std::cout << "merged size: " << merged.size() << std::endl;
auto mit = merged.begin();
for(bool finish = false; !finish && q != Q.end();) {
++q; // "Set i = i + 1"
while(!finish && mit != merged.end()) {
if(mit->isFrom(Rcont)) {
auto s = *mit;
s.dir = dir;
S.emplace_back(s);
}
if(mit->eq(*q)) {
S.emplace_back(*q);
if(mit->isTurningPoint()) dir = -dir;
if(q == Q.begin()) finish = true;
break;
}
mit += dir;
// __nfp::advance(mit, merged, dir > 0);
}
}
} else {
auto q = Q.rbegin();
S.emplace_back(*q);
// Roughly step 3
std::cout << "merged size: " << merged.size() << std::endl;
auto mit = merged.begin();
for(bool finish = false; !finish && q != Q.rend();) {
++q; // "Set i = i + 1"
while(!finish && mit != merged.end()) {
if(mit->isFrom(Rcont)) {
auto s = *mit;
s.dir = dir;
S.emplace_back(s);
}
if(mit->eq(*q)) {
S.emplace_back(*q);
S.back().dir = -1;
if(mit->isTurningPoint()) dir = -dir;
if(q == Q.rbegin()) finish = true;
break;
}
mit += dir;
// __nfp::advance(mit, merged, dir > 0);
}
}
}
// Step 4:
// "Let starting edge r1 be in position si in sequence"
// whaaat? I guess this means the following:
auto it = S.begin();
while(!it->eq(*R.begin())) ++it;
// "Set j = 1, next = 2, direction = 1, seq1 = si"
// we don't use j, seq is expanded dynamically.
dir = 1;
auto next = std::next(R.begin()); seq.emplace_back(*it);
// Step 5:
// "If all si edges have been allocated to seqj" should mean that
// we loop until seq has equal size with S
auto send = it; //it == S.begin() ? it : std::prev(it);
while(it != S.end()) {
++it; if(it == S.end()) it = S.begin();
if(it == send) break;
if(it->isFrom(Qcont)) {
seq.emplace_back(*it); // "If si is from Q, j = j + 1, seqj = si"
// "If si is a turning point in Q,
// direction = - direction, next = next + direction"
if(it->isTurningPoint()) {
dir = -dir;
next += dir;
// __nfp::advance(next, R, dir > 0);
}
}
if(it->eq(*next) /*&& dir == next->dir*/) { // "If si = direction.rnext"
// "j = j + 1, seqj = si, next = next + direction"
seq.emplace_back(*it);
next += dir;
// __nfp::advance(next, R, dir > 0);
}
}
return seq;
};
std::vector<EdgeRefList> seqlist;
seqlist.reserve(Bref.size());
EdgeRefList Bslope = Bref; // copy Bref, we will make a slope diagram
// make the slope diagram of B
std::sort(Bslope.begin(), Bslope.end(), sortfn);
auto slopeit = Bslope.begin(); // search for the first turning point
while(!slopeit->isTurningPoint() && slopeit != Bslope.end()) slopeit++;
if(slopeit == Bslope.end()) {
// no turning point means convex polygon.
seqlist.emplace_back(mink(Aref, Bref, true));
} else {
int dir = 1;
auto firstturn = Bref.begin();
while(!firstturn->eq(*slopeit)) ++firstturn;
assert(firstturn != Bref.end());
EdgeRefList bgroup; bgroup.reserve(Bref.size());
bgroup.emplace_back(*slopeit);
auto b_it = std::next(firstturn);
while(b_it != firstturn) {
if(b_it == Bref.end()) b_it = Bref.begin();
while(!slopeit->eq(*b_it)) {
__nfp::advance(slopeit, Bslope, dir > 0);
}
if(!slopeit->isTurningPoint()) {
bgroup.emplace_back(*slopeit);
} else {
if(!bgroup.empty()) {
if(dir > 0) bgroup.emplace_back(*slopeit);
for(auto& me : bgroup) {
std::cout << me.eref.get().label << ", ";
}
std::cout << std::endl;
seqlist.emplace_back(mink(Aref, bgroup, dir == 1 ? true : false));
bgroup.clear();
if(dir < 0) bgroup.emplace_back(*slopeit);
} else {
bgroup.emplace_back(*slopeit);
}
dir *= -1;
}
++b_it;
}
}
// while(it != Bref.end()) // This is step 3 and step 4 in one loop
// if(it->isTurningPoint()) {
// R = {R.last, it++};
// auto seq = mink(Q, R, orientation);
// // TODO step 6 (should be 5 shouldn't it?): linking edges from A
// // I don't get this step
// seqlist.insert(seqlist.end(), seq.begin(), seq.end());
// orientation = !orientation;
// } else ++it;
// if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true);
// /////////////////////////////////////////////////////////////////////////
// Algorithm 2: breaking Minkowski sums into track line trips
// /////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Algorithm 3: finding the boundary of the NFP from track line trips
// /////////////////////////////////////////////////////////////////////////
for(auto& seq : seqlist) {
std::cout << "seqlist size: " << seq.size() << std::endl;
for(auto& s : seq) {
std::cout << (s.dir > 0 ? "" : "-") << s.eref.get().label << ", ";
}
std::cout << std::endl;
}
auto& seq = seqlist.front();
RawShape rsh;
Vertex top_nfp;
std::vector<Edge> edgelist; edgelist.reserve(seq.size());
for(auto& s : seq) {
edgelist.emplace_back(s.eref.get().e);
}
__nfp::buildPolygon(edgelist, rsh, top_nfp);
return Result(rsh, top_nfp);
return {};
}
// Specializable NFP implementation class. Specialize it if you have a faster

View File

@ -8,13 +8,10 @@
#include <algorithm>
#include <functional>
#include "geometry_traits.hpp"
#include <libnest2d/geometry_traits.hpp>
namespace libnest2d {
namespace sl = shapelike;
namespace pl = pointlike;
/**
* \brief An item to be placed on a bin.
*
@ -422,13 +419,9 @@ private:
static inline bool vsort(const Vertex& v1, const Vertex& v2)
{
Coord &&x1 = getX(v1), &&x2 = getX(v2);
Coord &&y1 = getY(v1), &&y2 = getY(v2);
auto diff = y1 - y2;
if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
return x1 < x2;
return diff < 0;
TCompute<Vertex> x1 = getX(v1), x2 = getX(v2);
TCompute<Vertex> y1 = getY(v1), y2 = getY(v2);
return y1 == y2 ? x1 < x2 : y1 < y2;
}
};

View File

@ -4,7 +4,8 @@
#include <tuple>
#include <functional>
#include <limits>
#include "common.hpp"
#include <libnest2d/common.hpp>
namespace libnest2d { namespace opt {
@ -60,6 +61,7 @@ enum class Method {
L_SIMPLEX,
L_SUBPLEX,
G_GENETIC,
G_PARTICLE_SWARM
//...
};

View File

@ -48,7 +48,7 @@ else()
target_link_libraries(nloptOptimizer INTERFACE Nlopt::Nlopt)
endif()
#target_sources( NloptOptimizer INTERFACE
#target_sources( nloptOptimizer INTERFACE
#${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp
#${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp
#${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp
@ -57,5 +57,5 @@ endif()
target_compile_definitions(nloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT)
# And finally plug the NloptOptimizer into libnest2d
#target_link_libraries(libnest2d INTERFACE NloptOptimizer)
# And finally plug the nloptOptimizer into libnest2d
#target_link_libraries(libnest2d INTERFACE nloptOptimizer)

View File

@ -1,5 +0,0 @@
find_package(Armadillo REQUIRED)
add_library(OptimlibOptimizer INTERFACE)
target_include_directories(OptimlibOptimizer INTERFACE ${ARMADILLO_INCLUDE_DIRS})
target_link_libraries(OptimlibOptimizer INTERFACE ${ARMADILLO_LIBRARIES})

View File

@ -7,15 +7,15 @@
namespace libnest2d { namespace placers {
template<class T, class = T> struct Epsilon {};
template<class T, class = T> struct DefaultEpsilon {};
template<class T>
struct Epsilon<T, enable_if_t<std::is_integral<T>::value, T> > {
struct DefaultEpsilon<T, enable_if_t<std::is_integral<T>::value, T> > {
static const T Value = 1;
};
template<class T>
struct Epsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > {
struct DefaultEpsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > {
static const T Value = 1e-3;
};
@ -24,7 +24,7 @@ struct BLConfig {
DECLARE_MAIN_TYPES(RawShape);
Coord min_obj_distance = 0;
Coord epsilon = Epsilon<Coord>::Value;
Coord epsilon = DefaultEpsilon<Coord>::Value;
bool allow_rotations = false;
};

View File

@ -103,14 +103,14 @@ Key hash(const _Item<S>& item) {
while(deg > N) { ms++; deg -= N; }
ls += int(deg);
ret.push_back(char(ms)); ret.push_back(char(ls));
circ += seg.length();
circ += std::sqrt(seg.template sqlength<double>());
}
it = ctr.begin(); nx = std::next(it);
while(nx != ctr.end()) {
Segment seg(*it++, *nx++);
auto l = int(M * seg.length() / circ);
auto l = int(M * std::sqrt(seg.template sqlength<double>()) / circ);
int ms = 'A', ls = 'A';
while(l > N) { ms++; l -= N; }
ls += l;
@ -250,6 +250,11 @@ template<class RawShape> class EdgeCache {
double accuracy_ = 1.0;
static double length(const Edge &e)
{
return std::sqrt(e.template sqlength<double>());
}
void createCache(const RawShape& sh) {
{ // For the contour
auto first = shapelike::cbegin(sh);
@ -260,7 +265,7 @@ template<class RawShape> class EdgeCache {
while(next != endit) {
contour_.emap.emplace_back(*(first++), *(next++));
contour_.full_distance += contour_.emap.back().length();
contour_.full_distance += length(contour_.emap.back());
contour_.distances.emplace_back(contour_.full_distance);
}
}
@ -275,7 +280,7 @@ template<class RawShape> class EdgeCache {
while(next != endit) {
hc.emap.emplace_back(*(first++), *(next++));
hc.full_distance += hc.emap.back().length();
hc.full_distance += length(hc.emap.back());
hc.distances.emplace_back(hc.full_distance);
}

View File

@ -311,19 +311,19 @@ struct range_value<bp2d::Shapes> {
namespace libnest2d { // Now the algorithms that boost can provide...
namespace pointlike {
template<>
inline double distance(const PointImpl& p1, const PointImpl& p2 )
{
return boost::geometry::distance(p1, p2);
}
//namespace pointlike {
//template<>
//inline double distance(const PointImpl& p1, const PointImpl& p2 )
//{
// return boost::geometry::distance(p1, p2);
//}
template<>
inline double distance(const PointImpl& p, const bp2d::Segment& seg )
{
return boost::geometry::distance(p, seg);
}
}
//template<>
//inline double distance(const PointImpl& p, const bp2d::Segment& seg )
//{
// return boost::geometry::distance(p, seg);
//}
//}
namespace shapelike {
// Tell libnest2d how to make string out of a ClipperPolygon object
@ -382,16 +382,9 @@ inline bool touches( const PointImpl& point, const PolygonImpl& shape)
}
#ifndef DISABLE_BOOST_BOUNDING_BOX
template<>
inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&)
{
bp2d::Box b;
boost::geometry::envelope(sh, b);
return b;
}
template<>
inline bp2d::Box boundingBox(const PathImpl& sh, const PolygonTag&)
inline bp2d::Box boundingBox(const PathImpl& sh, const PathTag&)
{
bp2d::Box b;
boost::geometry::envelope(sh, b);
@ -410,9 +403,9 @@ inline bp2d::Box boundingBox<bp2d::Shapes>(const bp2d::Shapes& shapes,
#ifndef DISABLE_BOOST_CONVEX_HULL
template<>
inline PolygonImpl convexHull(const PolygonImpl& sh, const PolygonTag&)
inline PathImpl convexHull(const PathImpl& sh, const PathTag&)
{
PolygonImpl ret;
PathImpl ret;
boost::geometry::convex_hull(sh, ret);
return ret;
}

View File

@ -0,0 +1,268 @@
#ifndef ROTCALIPERS_HPP
#define ROTCALIPERS_HPP
#include <numeric>
#include <functional>
#include <array>
#include <cmath>
#include <libnest2d/geometry_traits.hpp>
namespace libnest2d {
template<class Pt, class Unit = TCompute<Pt>> class RotatedBox {
Pt axis_;
Unit bottom_ = Unit(0), right_ = Unit(0);
public:
RotatedBox() = default;
RotatedBox(const Pt& axis, Unit b, Unit r):
axis_(axis), bottom_(b), right_(r) {}
inline long double area() const {
long double asq = pl::magnsq<Pt, long double>(axis_);
return cast<long double>(bottom_) * cast<long double>(right_) / asq;
}
inline long double width() const {
return abs(bottom_) / std::sqrt(pl::magnsq<Pt, long double>(axis_));
}
inline long double height() const {
return abs(right_) / std::sqrt(pl::magnsq<Pt, long double>(axis_));
}
inline Unit bottom_extent() const { return bottom_; }
inline Unit right_extent() const { return right_; }
inline const Pt& axis() const { return axis_; }
inline Radians angleToX() const {
double ret = std::atan2(getY(axis_), getX(axis_));
auto s = std::signbit(ret);
if(s) ret += Pi_2;
return -ret;
}
};
template <class Poly, class Pt = TPoint<Poly>, class Unit = TCompute<Pt>>
Poly removeCollinearPoints(const Poly& sh, Unit eps = Unit(0))
{
Poly ret; sl::reserve(ret, sl::contourVertexCount(sh));
Pt eprev = *sl::cbegin(sh) - *std::prev(sl::cend(sh));
auto it = sl::cbegin(sh);
auto itx = std::next(it);
if(itx != sl::cend(sh)) while (it != sl::cend(sh))
{
Pt enext = *itx - *it;
auto dp = pl::dotperp<Pt, Unit>(eprev, enext);
if(abs(dp) > eps) sl::addVertex(ret, *it);
eprev = enext;
if (++itx == sl::cend(sh)) itx = sl::cbegin(sh);
++it;
}
return ret;
}
// The area of the bounding rectangle with the axis dir and support vertices
template<class Pt, class Unit = TCompute<Pt>, class R = TCompute<Pt>>
inline R rectarea(const Pt& w, // the axis
const Pt& vb, const Pt& vr,
const Pt& vt, const Pt& vl)
{
Unit a = pl::dot<Pt, Unit>(w, vr - vl);
Unit b = pl::dot<Pt, Unit>(-pl::perp(w), vt - vb);
R m = R(a) / pl::magnsq<Pt, Unit>(w);
m = m * b;
return m;
};
template<class Pt,
class Unit = TCompute<Pt>,
class R = TCompute<Pt>,
class It = typename std::vector<Pt>::const_iterator>
inline R rectarea(const Pt& w, const std::array<It, 4>& rect)
{
return rectarea<Pt, Unit, R>(w, *rect[0], *rect[1], *rect[2], *rect[3]);
}
// This function is only applicable to counter-clockwise oriented convex
// polygons where only two points can be collinear witch each other.
template <class RawShape,
class Unit = TCompute<RawShape>,
class Ratio = TCompute<RawShape>>
RotatedBox<TPoint<RawShape>, Unit> minAreaBoundingBox(const RawShape& sh)
{
using Point = TPoint<RawShape>;
using Iterator = typename TContour<RawShape>::const_iterator;
using pointlike::dot; using pointlike::magnsq; using pointlike::perp;
// Get the first and the last vertex iterator
auto first = sl::cbegin(sh);
auto last = std::prev(sl::cend(sh));
// Check conditions and return undefined box if input is not sane.
if(last == first) return {};
if(getX(*first) == getX(*last) && getY(*first) == getY(*last)) --last;
if(last - first < 2) return {};
RawShape shcpy; // empty at this point
{
Point p = *first, q = *std::next(first), r = *last;
// Determine orientation from first 3 vertex (should be consistent)
Unit d = (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) -
(Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p));
if(d > 0) {
// The polygon is clockwise. A flip is needed (for now)
sl::reserve(shcpy, last - first);
auto it = last; while(it != first) sl::addVertex(shcpy, *it--);
sl::addVertex(shcpy, *first);
first = sl::cbegin(shcpy); last = std::prev(sl::cend(shcpy));
}
}
// Cyclic iterator increment
auto inc = [&first, &last](Iterator& it) {
if(it == last) it = first; else ++it;
};
// Cyclic previous iterator
auto prev = [&first, &last](Iterator it) {
return it == first ? last : std::prev(it);
};
// Cyclic next iterator
auto next = [&first, &last](Iterator it) {
return it == last ? first : std::next(it);
};
// Establish initial (axis aligned) rectangle support verices by determining
// polygon extremes:
auto it = first;
Iterator minX = it, maxX = it, minY = it, maxY = it;
do { // Linear walk through the vertices and save the extreme positions
Point v = *it, d = v - *minX;
if(getX(d) < 0 || (getX(d) == 0 && getY(d) < 0)) minX = it;
d = v - *maxX;
if(getX(d) > 0 || (getX(d) == 0 && getY(d) > 0)) maxX = it;
d = v - *minY;
if(getY(d) < 0 || (getY(d) == 0 && getX(d) > 0)) minY = it;
d = v - *maxY;
if(getY(d) > 0 || (getY(d) == 0 && getX(d) < 0)) maxY = it;
} while(++it != std::next(last));
// Update the vertices defining the bounding rectangle. The rectangle with
// the smallest rotation is selected and the supporting vertices are
// returned in the 'rect' argument.
auto update = [&next, &inc]
(const Point& w, std::array<Iterator, 4>& rect)
{
Iterator B = rect[0], Bn = next(B);
Iterator R = rect[1], Rn = next(R);
Iterator T = rect[2], Tn = next(T);
Iterator L = rect[3], Ln = next(L);
Point b = *Bn - *B, r = *Rn - *R, t = *Tn - *T, l = *Ln - *L;
Point pw = perp(w);
using Pt = Point;
Unit dotwpb = dot<Pt, Unit>( w, b), dotwpr = dot<Pt, Unit>(-pw, r);
Unit dotwpt = dot<Pt, Unit>(-w, t), dotwpl = dot<Pt, Unit>( pw, l);
Unit dw = magnsq<Pt, Unit>(w);
std::array<Ratio, 4> angles;
angles[0] = (Ratio(dotwpb) / magnsq<Pt, Unit>(b)) * dotwpb;
angles[1] = (Ratio(dotwpr) / magnsq<Pt, Unit>(r)) * dotwpr;
angles[2] = (Ratio(dotwpt) / magnsq<Pt, Unit>(t)) * dotwpt;
angles[3] = (Ratio(dotwpl) / magnsq<Pt, Unit>(l)) * dotwpl;
using AngleIndex = std::pair<Ratio, size_t>;
std::vector<AngleIndex> A; A.reserve(4);
for (size_t i = 3, j = 0; j < 4; i = j++) {
if(rect[i] != rect[j] && angles[i] < dw) {
auto iv = std::make_pair(angles[i], i);
auto it = std::lower_bound(A.begin(), A.end(), iv,
[](const AngleIndex& ai,
const AngleIndex& aj)
{
return ai.first > aj.first;
});
A.insert(it, iv);
}
}
// The polygon is supposed to be a rectangle.
if(A.empty()) return false;
auto amin = A.front().first;
auto imin = A.front().second;
for(auto& a : A) if(a.first == amin) inc(rect[a.second]);
std::rotate(rect.begin(), rect.begin() + imin, rect.end());
return true;
};
Point w(1, 0);
Point w_min = w;
Ratio minarea((Unit(getX(*maxX)) - getX(*minX)) *
(Unit(getY(*maxY)) - getY(*minY)));
std::array<Iterator, 4> rect = {minY, maxX, maxY, minX};
std::array<Iterator, 4> minrect = rect;
// An edge might be examined twice in which case the algorithm terminates.
size_t c = 0, count = last - first + 1;
std::vector<bool> edgemask(count, false);
while(c++ < count)
{
// Update the support vertices, if cannot be updated, break the cycle.
if(! update(w, rect)) break;
size_t eidx = size_t(rect[0] - first);
if(edgemask[eidx]) break;
edgemask[eidx] = true;
// get the unnormalized direction vector
w = *rect[0] - *prev(rect[0]);
// get the area of the rotated rectangle
Ratio rarea = rectarea<Point, Unit, Ratio>(w, rect);
// Update min area and the direction of the min bounding box;
if(rarea <= minarea) { w_min = w; minarea = rarea; minrect = rect; }
}
Unit a = dot<Point, Unit>(w_min, *minrect[1] - *minrect[3]);
Unit b = dot<Point, Unit>(-perp(w_min), *minrect[2] - *minrect[0]);
RotatedBox<Point, Unit> bb(w_min, a, b);
return bb;
}
template <class RawShape> Radians minAreaBoundingBoxRotation(const RawShape& sh)
{
return minAreaBoundingBox(sh).angleToX();
}
}
#endif // ROTCALIPERS_HPP

View File

@ -0,0 +1,23 @@
#include <libnest2d.h>
namespace libnest2d {
template class Nester<NfpPlacer, FirstFitSelection>;
template class Nester<BottomLeftPlacer, FirstFitSelection>;
template PackGroup nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
template PackGroup nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to,
const Box& bin,
ProgressFunction prg,
StopCondition scond,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
}

View File

@ -49,7 +49,12 @@ add_executable(tests_clipper_nlopt
target_link_libraries(tests_clipper_nlopt libnest2d ${GTEST_LIBS_TO_LINK} )
target_include_directories(tests_clipper_nlopt PRIVATE BEFORE
${GTEST_INCLUDE_DIRS})
target_include_directories(tests_clipper_nlopt PRIVATE BEFORE ${GTEST_INCLUDE_DIRS})
if(NOT LIBNEST2D_HEADER_ONLY)
target_link_libraries(tests_clipper_nlopt ${LIBNAME})
else()
target_link_libraries(tests_clipper_nlopt libnest2d)
endif()
add_test(libnest2d_tests tests_clipper_nlopt)

View File

@ -3,11 +3,43 @@
#include <libnest2d.h>
#include "printer_parts.h"
#include <libnest2d/geometry_traits_nfp.hpp>
//#include <libnest2d/geometry_traits_nfp.hpp>
#include "../tools/svgtools.hpp"
#include <libnest2d/utils/rotcalipers.hpp>
#include "boost/multiprecision/integer.hpp"
#include "boost/rational.hpp"
//#include "../tools/Int128.hpp"
//#include "gte/Mathematics/GteMinimumAreaBox2.h"
//#include "../tools/libnfpglue.hpp"
//#include "../tools/nfp_svgnest_glue.hpp"
namespace libnest2d {
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
using LargeInt = __int128;
#else
using LargeInt = boost::multiprecision::int128_t;
template<> struct _NumTag<LargeInt> { using Type = ScalarTag; };
#endif
template<class T> struct _NumTag<boost::rational<T>> { using Type = RationalTag; };
namespace nfp {
template<class S>
struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
{
NfpResult<S> operator()(const S &sh, const S &other)
{
return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other);
}
};
}
}
std::vector<libnest2d::Item>& prusaParts() {
static std::vector<libnest2d::Item> ret;
@ -31,8 +63,8 @@ TEST(BasicFunctionality, Angles)
ASSERT_DOUBLE_EQ(rad, Pi);
ASSERT_DOUBLE_EQ(deg, 180);
ASSERT_DOUBLE_EQ(deg2, 180);
ASSERT_DOUBLE_EQ(rad, (Radians) deg);
ASSERT_DOUBLE_EQ( (Degrees) rad, deg);
ASSERT_DOUBLE_EQ(rad, Radians(deg));
ASSERT_DOUBLE_EQ( Degrees(rad), deg);
ASSERT_TRUE(rad == deg);
@ -151,12 +183,12 @@ TEST(GeometryAlgorithms, Distance) {
Segment seg(p1, p3);
ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755);
// ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755);
auto result = pointlike::horizontalDistance(p2, seg);
auto check = [](Coord val, Coord expected) {
if(std::is_floating_point<Coord>::value)
auto check = [](TCompute<Coord> val, TCompute<Coord> expected) {
if(std::is_floating_point<TCompute<Coord>>::value)
ASSERT_DOUBLE_EQ(static_cast<double>(val),
static_cast<double>(expected));
else
@ -415,7 +447,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose)
namespace {
using namespace libnest2d;
template<unsigned long SCALE = 1, class Bin>
template<long long SCALE = 1, class Bin>
void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin, int idx = 0) {
@ -500,6 +532,41 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) {
}
}
TEST(GeometryAlgorithms, convexHull) {
using namespace libnest2d;
ClipperLib::Path poly = PRINTER_PART_POLYGONS[0];
auto chull = sl::convexHull(poly);
ASSERT_EQ(chull.size(), poly.size());
}
TEST(GeometryAlgorithms, NestTest) {
std::vector<Item> input = prusaParts();
PackGroup result = libnest2d::nest(input,
Box(250000000, 210000000),
[](unsigned cnt) {
std::cout
<< "parts left: " << cnt
<< std::endl;
});
ASSERT_LE(result.size(), 2);
int partsum = std::accumulate(result.begin(),
result.end(),
0,
[](int s,
const decltype(result)::value_type &bin) {
return s += bin.size();
});
ASSERT_EQ(input.size(), partsum);
}
namespace {
struct ItemPair {
@ -713,7 +780,7 @@ void testNfp(const std::vector<ItemPair>& testdata) {
auto& exportfun = exportSVG<SCALE, Box>;
auto onetest = [&](Item& orbiter, Item& stationary, unsigned testidx){
auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){
testcase++;
orbiter.translate({210*SCALE, 0});
@ -820,7 +887,7 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) {
rect2.translate({10, 0});
rect3.translate({25, 0});
shapelike::Shapes<PolygonImpl> pile;
TMultiShape<PolygonImpl> pile;
pile.push_back(rect1.transformedShape());
pile.push_back(rect2.transformedShape());
@ -833,6 +900,126 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) {
ASSERT_EQ(shapelike::area(result.front()), ref.area());
}
namespace {
long double refMinAreaBox(const PolygonImpl& p) {
auto it = sl::cbegin(p), itx = std::next(it);
long double min_area = std::numeric_limits<long double>::max();
auto update_min = [&min_area, &it, &itx, &p]() {
Segment s(*it, *itx);
PolygonImpl rotated = p;
sl::rotate(rotated, -s.angleToXaxis());
auto bb = sl::boundingBox(rotated);
auto area = cast<long double>(sl::area(bb));
if(min_area > area) min_area = area;
};
while(itx != sl::cend(p)) {
update_min();
++it; ++itx;
}
it = std::prev(sl::cend(p)); itx = sl::cbegin(p);
update_min();
return min_area;
}
template<class T> struct BoostGCD {
T operator()(const T &a, const T &b) { return boost::gcd(a, b); }
};
using Unit = int64_t;
using Ratio = boost::rational<boost::multiprecision::int128_t>;// Rational<boost::multiprecision::int256_t>;
//double gteMinAreaBox(const PolygonImpl& p) {
// using GteCoord = ClipperLib::cInt;
// using GtePoint = gte::Vector2<GteCoord>;
// gte::MinimumAreaBox2<GteCoord, Ratio> mb;
// std::vector<GtePoint> points;
// points.reserve(p.Contour.size());
// for(auto& pt : p.Contour) points.emplace_back(GtePoint{GteCoord(pt.X), GteCoord(pt.Y)});
// mb(int(points.size()), points.data(), 0, nullptr, true);
// auto min_area = double(mb.GetArea());
// return min_area;
//}
}
TEST(RotatingCalipers, MinAreaBBCClk) {
// PolygonImpl poly({{-50, 30}, {-50, -50}, {50, -50}, {50, 50}, {-40, 50}});
// PolygonImpl poly({{-50, 0}, {50, 0}, {0, 100}});
auto u = [](ClipperLib::cInt n) { return n*1000000; };
PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}});
long double arearef = refMinAreaBox(poly);
long double area = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly).area();
// double gtearea = gteMinAreaBox(poly);
ASSERT_LE(std::abs(area - arearef), 500e6 );
// ASSERT_LE(std::abs(gtearea - arearef), 500 );
// ASSERT_DOUBLE_EQ(gtearea, arearef);
}
TEST(RotatingCalipers, AllPrusaMinBB) {
size_t idx = 0;
long double err_epsilon = 500e6l;
for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) {
// ClipperLib::Path rinput = PRINTER_PART_POLYGONS[idx];
// rinput.pop_back();
// std::reverse(rinput.begin(), rinput.end());
// PolygonImpl poly(removeCollinearPoints<PathImpl, PointImpl, Unit>(rinput, 1000000));
PolygonImpl poly(rinput);
long double arearef = refMinAreaBox(poly);
auto bb = minAreaBoundingBox<PathImpl, Unit, Ratio>(rinput);
long double area = cast<long double>(bb.area());
// double area = gteMinAreaBox(poly);
bool succ = std::abs(arearef - area) < err_epsilon;
std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: "
<< arearef << " actual: " << area << std::endl;
ASSERT_TRUE(succ);
}
for(ClipperLib::Path rinput : STEGOSAUR_POLYGONS) {
rinput.pop_back();
std::reverse(rinput.begin(), rinput.end());
PolygonImpl poly(removeCollinearPoints<PathImpl, PointImpl, Unit>(rinput, 1000000));
long double arearef = refMinAreaBox(poly);
auto bb = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly);
long double area = cast<long double>(bb.area());
// double area = gteMinAreaBox(poly);
bool succ = std::abs(arearef - area) < err_epsilon;
std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: "
<< arearef << " actual: " << area << std::endl;
ASSERT_TRUE(succ);
}
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();

View File

@ -160,6 +160,8 @@ add_library(libslic3r STATIC
MTUtils.hpp
Zipper.hpp
Zipper.cpp
MinAreaBoundingBox.hpp
MinAreaBoundingBox.cpp
miniz_extension.hpp
miniz_extension.cpp
SLA/SLABoilerPlate.hpp

View File

@ -37,6 +37,8 @@
* *
*******************************************************************************/
#ifndef SLIC3R_INT128_HPP
#define SLIC3R_INT128_HPP
// #define SLIC3R_DEBUG
// Make assert active if SLIC3R_DEBUG
@ -48,6 +50,8 @@
#endif
#include <cassert>
#include <cstdint>
#include <cmath>
#if ! defined(_MSC_VER) && defined(__SIZEOF_INT128__)
#define HAS_INTRINSIC_128_TYPE
@ -293,3 +297,5 @@ public:
return sign_determinant_2x2(p1, q1, p2, q2) * invert;
}
};
#endif // SLIC3R_INT128_HPP

View File

@ -0,0 +1,142 @@
#include "MinAreaBoundingBox.hpp"
#include <libslic3r/ExPolygon.hpp>
#include <boost/rational.hpp>
#include <libslic3r/Int128.hpp>
#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__)
#include <boost/multiprecision/integer.hpp>
#endif
#include <libnest2d/geometry_traits.hpp>
#include <libnest2d/utils/rotcalipers.hpp>
namespace libnest2d {
template<> struct PointType<Slic3r::Points> { using Type = Slic3r::Point; };
template<> struct CoordType<Slic3r::Point> { using Type = coord_t; };
template<> struct ShapeTag<Slic3r::ExPolygon> { using Type = PolygonTag; };
template<> struct ShapeTag<Slic3r::Polygon> { using Type = PolygonTag; };
template<> struct ShapeTag<Slic3r::Points> { using Type = PathTag; };
template<> struct ShapeTag<Slic3r::Point> { using Type = PointTag; };
template<> struct ContourType<Slic3r::ExPolygon> { using Type = Slic3r::Points; };
template<> struct ContourType<Slic3r::Polygon> { using Type = Slic3r::Points; };
namespace pointlike {
template<> inline coord_t x(const Slic3r::Point& p) { return p.x(); }
template<> inline coord_t y(const Slic3r::Point& p) { return p.y(); }
template<> inline coord_t& x(Slic3r::Point& p) { return p.x(); }
template<> inline coord_t& y(Slic3r::Point& p) { return p.y(); }
} // pointlike
namespace shapelike {
template<> inline Slic3r::Points& contour(Slic3r::ExPolygon& sh) { return sh.contour.points; }
template<> inline const Slic3r::Points& contour(const Slic3r::ExPolygon& sh) { return sh.contour.points; }
template<> inline Slic3r::Points& contour(Slic3r::Polygon& sh) { return sh.points; }
template<> inline const Slic3r::Points& contour(const Slic3r::Polygon& sh) { return sh.points; }
template<> Slic3r::Points::iterator begin(Slic3r::Points& pts, const PathTag&) { return pts.begin();}
template<> Slic3r::Points::const_iterator cbegin(const Slic3r::Points& pts, const PathTag&) { return pts.begin(); }
template<> Slic3r::Points::iterator end(Slic3r::Points& pts, const PathTag&) { return pts.end();}
template<> Slic3r::Points::const_iterator cend(const Slic3r::Points& pts, const PathTag&) { return pts.cend(); }
template<> inline Slic3r::ExPolygon create<Slic3r::ExPolygon>(Slic3r::Points&& contour)
{
Slic3r::ExPolygon expoly; expoly.contour.points.swap(contour);
return expoly;
}
template<> inline Slic3r::Polygon create<Slic3r::Polygon>(Slic3r::Points&& contour)
{
Slic3r::Polygon poly; poly.points.swap(contour);
return poly;
}
} // shapelike
} // libnest2d
namespace Slic3r {
// Used as compute type.
using Unit = int64_t;
#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__)
using Rational = boost::rational<boost::multiprecision::int128_t>;
#else
using Rational = boost::rational<__int128>;
#endif
MinAreaBoundigBox::MinAreaBoundigBox(const Polygon &p, PolygonLevel pc)
{
const Polygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p);
libnest2d::RotatedBox<Point, Unit> box =
libnest2d::minAreaBoundingBox<Polygon, Unit, Rational>(chull);
m_right = box.right_extent();
m_bottom = box.bottom_extent();
m_axis = box.axis();
}
MinAreaBoundigBox::MinAreaBoundigBox(const ExPolygon &p, PolygonLevel pc)
{
const ExPolygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p);
libnest2d::RotatedBox<Point, Unit> box =
libnest2d::minAreaBoundingBox<ExPolygon, Unit, Rational>(chull);
m_right = box.right_extent();
m_bottom = box.bottom_extent();
m_axis = box.axis();
}
MinAreaBoundigBox::MinAreaBoundigBox(const Points &pts, PolygonLevel pc)
{
const Points& chull = pc == pcConvex ? pts : libnest2d::sl::convexHull(pts);
libnest2d::RotatedBox<Point, Unit> box =
libnest2d::minAreaBoundingBox<Points, Unit, Rational>(chull);
m_right = box.right_extent();
m_bottom = box.bottom_extent();
m_axis = box.axis();
}
double MinAreaBoundigBox::angle_to_X() const
{
double ret = std::atan2(m_axis.y(), m_axis.x());
auto s = std::signbit(ret);
if(s) ret += 2 * PI;
return -ret;
}
long double MinAreaBoundigBox::width() const
{
return std::abs(m_bottom) / std::sqrt(libnest2d::pl::magnsq<Point, long double>(m_axis));
}
long double MinAreaBoundigBox::height() const
{
return std::abs(m_right) / std::sqrt(libnest2d::pl::magnsq<Point, long double>(m_axis));
}
long double MinAreaBoundigBox::area() const
{
long double asq = libnest2d::pl::magnsq<Point, long double>(m_axis);
return m_bottom * m_right / asq;
}
void remove_collinear_points(Polygon &p)
{
p = libnest2d::removeCollinearPoints<Polygon>(p, Unit(0));
}
void remove_collinear_points(ExPolygon &p)
{
p = libnest2d::removeCollinearPoints<ExPolygon>(p, Unit(0));
}
}

View File

@ -0,0 +1,59 @@
#ifndef MINAREABOUNDINGBOX_HPP
#define MINAREABOUNDINGBOX_HPP
#include "libslic3r/Point.hpp"
namespace Slic3r {
class Polygon;
class ExPolygon;
void remove_collinear_points(Polygon& p);
void remove_collinear_points(ExPolygon& p);
/// A class that holds a rotated bounding box. If instantiated with a polygon
/// type it will hold the minimum area bounding box for the given polygon.
/// If the input polygon is convex, the complexity is linear to the number of
/// points. Otherwise a convex hull of O(n*log(n)) has to be performed.
class MinAreaBoundigBox {
Point m_axis;
long double m_bottom = 0.0l, m_right = 0.0l;
public:
// Polygons can be convex or simple (convex or concave with possible holes)
enum PolygonLevel {
pcConvex, pcSimple
};
// Constructors with various types of geometry data used in Slic3r.
// If the convexity is known apriory, pcConvex can be used to skip
// convex hull calculation. It is very important that the input polygons
// do NOT have any collinear points (except for the first and the last
// vertex being the same -- meaning a closed polygon for boost)
// To make sure this constraint is satisfied, you can call
// remove_collinear_points on the input polygon before handing over here)
explicit MinAreaBoundigBox(const Polygon&, PolygonLevel = pcSimple);
explicit MinAreaBoundigBox(const ExPolygon&, PolygonLevel = pcSimple);
explicit MinAreaBoundigBox(const Points&, PolygonLevel = pcSimple);
// Returns the angle in radians needed for the box to be aligned with the
// X axis. Rotate the polygon by this angle and it will be aligned.
double angle_to_X() const;
// The box width
long double width() const;
// The box height
long double height() const;
// The box area
long double area() const;
// The axis of the rotated box. If the angle_to_X is not sufficiently
// precise, use this unnormalized direction vector.
const Point& axis() const { return m_axis; }
};
}
#endif // MINAREABOUNDINGBOX_HPP

View File

@ -9,6 +9,31 @@
#include <ClipperUtils.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <boost/multiprecision/integer.hpp>
#include <boost/rational.hpp>
namespace libnest2d {
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
using LargeInt = __int128;
#else
using LargeInt = boost::multiprecision::int128_t;
template<> struct _NumTag<LargeInt> { using Type = ScalarTag; };
#endif
template<class T> struct _NumTag<boost::rational<T>> { using Type = RationalTag; };
namespace nfp {
template<class S>
struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
{
NfpResult<S> operator()(const S &sh, const S &other)
{
return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other);
}
};
}
}
namespace Slic3r {
@ -130,7 +155,7 @@ Box boundingBox(const Box& pilebb, const Box& ibb ) {
// at the same time, it has to provide reasonable results.
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
objfunc(const PointImpl& bincenter,
const shapelike::Shapes<PolygonImpl>& merged_pile,
const TMultiShape<PolygonImpl>& merged_pile,
const Box& pilebb,
const ItemGroup& items,
const Item &item,
@ -293,7 +318,7 @@ class AutoArranger {};
// management and spatial index structures for acceleration.
template<class TBin>
class _ArrBase {
protected:
public:
// Useful type shortcuts...
using Placer = TPacker<TBin>;
@ -301,7 +326,9 @@ protected:
using Packer = Nester<Placer, Selector>;
using PConfig = typename Packer::PlacementConfig;
using Distance = TCoord<PointImpl>;
using Pile = sl::Shapes<PolygonImpl>;
using Pile = TMultiShape<PolygonImpl>;
protected:
Packer m_pck;
PConfig m_pconf; // Placement configuration
@ -539,7 +566,10 @@ public:
// 2D shape from top view.
using ShapeData2D = std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo& wti) {
ShapeData2D projectModelFromTop(const Slic3r::Model &model,
const WipeTowerInfo &wti,
double tolerance)
{
ShapeData2D ret;
// Count all the items on the bin (all the object's instances)
@ -564,18 +594,29 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo&
ModelInstance *finst = objptr->instances.front();
Vec3d rotation = finst->get_rotation();
rotation.z() = 0.;
Transform3d trafo_instance = Geometry::assemble_transform(Vec3d::Zero(), rotation, finst->get_scaling_factor(), finst->get_mirror());
Transform3d trafo_instance = Geometry::assemble_transform(
Vec3d::Zero(),
rotation,
finst->get_scaling_factor(),
finst->get_mirror());
Polygon p = objptr->convex_hull_2d(trafo_instance);
assert(! p.points.empty());
// this may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209
if (p.points.empty())
continue;
assert(!p.points.empty());
// this may happen for malformed models, see:
// https://github.com/prusa3d/PrusaSlicer/issues/2209
if (p.points.empty()) continue;
if(tolerance > EPSILON) {
Polygons pp { p };
pp = p.simplify(double(scaled(tolerance)));
if (!pp.empty()) p = pp.front();
}
p.reverse();
assert(!p.is_counter_clockwise());
p.append(p.first_point());
clpath = Slic3rMultiPoint_to_ClipperPath(p);
auto firstp = clpath.front(); clpath.emplace_back(firstp);
}
Vec3d rotation0 = objptr->instances.front()->get_rotation();
@ -589,7 +630,7 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo&
// Invalid geometries would throw exceptions when arranging
if(item.vertexCount() > 3) {
item.rotation(float(Geometry::rotation_diff_z(rotation0, objinst->get_rotation()))),
item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation()));
item.translation({
ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR),
ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR)
@ -757,7 +798,7 @@ bool arrange(Model &model, // The model with the geometries
bool ret = true;
// Get the 2D projected shapes with their 3D model instance pointers
auto shapemap = arr::projectModelFromTop(model, wti);
auto shapemap = arr::projectModelFromTop(model, wti, 0.1);
// Copy the references for the shapes only as the arranger expects a
// sequence of objects convertible to Item or ClipperPolygon
@ -858,7 +899,7 @@ void find_new_position(const Model &model,
WipeTowerInfo& wti)
{
// Get the 2D projected shapes with their 3D model instance pointers
auto shapemap = arr::projectModelFromTop(model, wti);
auto shapemap = arr::projectModelFromTop(model, wti, 0.1);
// Copy the references for the shapes only as the arranger expects a
// sequence of objects convertible to Item or ClipperPolygon

View File

@ -39,7 +39,12 @@
#include "libslic3r/SLA/SLARotfinder.hpp"
#include "libslic3r/Utils.hpp"
#include "libnest2d/optimizers/nlopt/genetic.hpp"
//#include "libslic3r/ClipperUtils.hpp"
// #include "libnest2d/optimizers/nlopt/genetic.hpp"
// #include "libnest2d/backends/clipper/geometries.hpp"
// #include "libnest2d/utils/rotcalipers.hpp"
#include "libslic3r/MinAreaBoundingBox.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
@ -2468,8 +2473,9 @@ void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process()
},
[this]() { return was_canceled(); });
const auto *bed_shape_opt = plater().config->opt<ConfigOptionPoints>(
"bed_shape");
const auto *bed_shape_opt =
plater().config->opt<ConfigOptionPoints>("bed_shape");
assert(bed_shape_opt);
auto & bedpoints = bed_shape_opt->values;
@ -2478,53 +2484,24 @@ void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process()
for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
double mindist = 6.0; // FIXME
double offs = mindist / 2.0 - EPSILON;
if (!was_canceled()) // wasn't canceled
for (ModelInstance *oi : o->instances) {
if (!was_canceled()) {
for(ModelInstance * oi : o->instances) {
oi->set_rotation({r[X], r[Y], r[Z]});
auto trchull = o->convex_hull_2d(
oi->get_transformation().get_matrix());
auto trmatrix = oi->get_transformation().get_matrix();
Polygon trchull = o->convex_hull_2d(trmatrix);
namespace opt = libnest2d::opt;
opt::StopCriteria stopcr;
stopcr.relative_score_difference = 0.01;
stopcr.max_iterations = 10000;
stopcr.stop_score = 0.0;
opt::GeneticOptimizer solver(stopcr);
Polygon pbed(bed);
MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
double r = rotbb.angle_to_X();
auto bin = pbed.bounding_box();
double binw = bin.size()(X) * SCALING_FACTOR - offs;
double binh = bin.size()(Y) * SCALING_FACTOR - offs;
// The box should be landscape
if(rotbb.width() < rotbb.height()) r += PI / 2;
auto result = solver.optimize_min(
[&trchull, binw, binh](double rot) {
auto chull = trchull;
chull.rotate(rot);
Vec3d rt = oi->get_rotation(); rt(Z) += r;
auto bb = chull.bounding_box();
double bbw = bb.size()(X) * SCALING_FACTOR;
double bbh = bb.size()(Y) * SCALING_FACTOR;
auto wdiff = bbw - binw;
auto hdiff = bbh - binh;
double diff = 0;
if (wdiff < 0 && hdiff < 0) diff = wdiff + hdiff;
if (wdiff > 0) diff += wdiff;
if (hdiff > 0) diff += hdiff;
return diff;
},
opt::initvals(0.0),
opt::bound(-PI / 2, PI / 2));
double r = std::get<0>(result.optimum);
Vec3d rt = oi->get_rotation();
rt(Z) += r;
oi->set_rotation(rt);
}
arr::WipeTowerInfo wti; // useless in SLA context
arr::find_new_position(plater().model,
@ -2536,12 +2513,11 @@ void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process()
// Correct the z offset of the object which was corrupted be
// the rotation
o->ensure_on_bed();
}
update_status(100, _(L("Orientation found.")));
}
else {
update_status(100, _(L("Orientation search canceled.")));
}
update_status(100,
was_canceled() ? _(L("Orientation search canceled."))
: _(L("Orientation found.")));
}
void Plater::priv::split_object()