diff --git a/.clang-format b/.clang-format index 1cfcefeb2..6b68254ec 100644 --- a/.clang-format +++ b/.clang-format @@ -19,7 +19,7 @@ AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false -BinPackArguments: false +BinPackArguments: true BinPackParameters: false BraceWrapping: AfterClass: true diff --git a/src/libnest2d/include/libnest2d.h b/src/libnest2d/include/libnest2d.h index a6eb36a4b..4661b4574 100644 --- a/src/libnest2d/include/libnest2d.h +++ b/src/libnest2d/include/libnest2d.h @@ -30,9 +30,7 @@ using Circle = _Circle; using Item = _Item; using Rectangle = _Rectangle; - using PackGroup = _PackGroup; -using IndexedPackGroup = _IndexedPackGroup; using FillerSelection = selections::_FillerSelection; using FirstFitSelection = selections::_FirstFitSelection; @@ -61,20 +59,20 @@ extern template PackGroup Nester::execute( template::iterator> -PackGroup nest(Iterator from, Iterator to, +void nest(Iterator from, Iterator to, const typename Placer::BinType& bin, Coord dist = 0, const typename Placer::Config& pconf = {}, const typename Selector::Config& sconf = {}) { - Nester nester(bin, dist, pconf, sconf); - return nester.execute(from, to); + _Nester nester(bin, dist, pconf, sconf); + nester.execute(from, to); } template::iterator> -PackGroup nest(Iterator from, Iterator to, +void nest(Iterator from, Iterator to, const typename Placer::BinType& bin, ProgressFunction prg, StopCondition scond = []() { return false; }, @@ -82,10 +80,10 @@ PackGroup nest(Iterator from, Iterator to, const typename Placer::Config& pconf = {}, const typename Selector::Config& sconf = {}) { - Nester nester(bin, dist, pconf, sconf); + _Nester nester(bin, dist, pconf, sconf); if(prg) nester.progressIndicator(prg); if(scond) nester.stopCondition(scond); - return nester.execute(from, to); + nester.execute(from, to); } #ifdef LIBNEST2D_STATIC @@ -93,14 +91,14 @@ PackGroup nest(Iterator from, Iterator to, extern template class Nester; extern template class Nester; -extern template PackGroup nest(std::vector::iterator from, +extern template void nest(std::vector::iterator from, std::vector::iterator to, const Box& bin, Coord dist = 0, const NfpPlacer::Config& pconf, const FirstFitSelection::Config& sconf); -extern template PackGroup nest(std::vector::iterator from, +extern template void nest(std::vector::iterator from, std::vector::iterator to, const Box& bin, ProgressFunction prg, @@ -114,20 +112,19 @@ extern template PackGroup nest(std::vector::iterator from, template> -PackGroup nest(Container&& cont, +void nest(Container&& cont, const typename Placer::BinType& bin, Coord dist = 0, const typename Placer::Config& pconf = {}, const typename Selector::Config& sconf = {}) { - return nest(cont.begin(), cont.end(), - bin, dist, pconf, sconf); + nest(cont.begin(), cont.end(), bin, dist, pconf, sconf); } template> -PackGroup nest(Container&& cont, +void nest(Container&& cont, const typename Placer::BinType& bin, ProgressFunction prg, StopCondition scond = []() { return false; }, @@ -135,8 +132,8 @@ PackGroup nest(Container&& cont, const typename Placer::Config& pconf = {}, const typename Selector::Config& sconf = {}) { - return nest(cont.begin(), cont.end(), - bin, prg, scond, dist, pconf, sconf); + nest(cont.begin(), cont.end(), bin, prg, scond, dist, + pconf, sconf); } } diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp index e9fad405b..06afbd187 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp @@ -41,25 +41,25 @@ template<> struct HolesContainer { using Type = ClipperLib::Paths; namespace pointlike { // Tell libnest2d how to extract the X coord from a ClipperPoint object -template<> inline TCoord x(const PointImpl& p) +template<> inline ClipperLib::cInt x(const PointImpl& p) { return p.X; } // Tell libnest2d how to extract the Y coord from a ClipperPoint object -template<> inline TCoord y(const PointImpl& p) +template<> inline ClipperLib::cInt y(const PointImpl& p) { return p.Y; } // Tell libnest2d how to extract the X coord from a ClipperPoint object -template<> inline TCoord& x(PointImpl& p) +template<> inline ClipperLib::cInt& x(PointImpl& p) { return p.X; } // Tell libnest2d how to extract the Y coord from a ClipperPoint object -template<> inline TCoord& y(PointImpl& p) +template<> inline ClipperLib::cInt& y(PointImpl& p) { return p.Y; } @@ -71,7 +71,8 @@ template<> inline TCoord& y(PointImpl& p) namespace shapelike { -template<> inline void offset(PolygonImpl& sh, TCoord distance) +template<> +inline void offset(PolygonImpl& sh, TCoord distance, const PolygonTag&) { #define DISABLE_BOOST_OFFSET @@ -123,6 +124,14 @@ template<> inline void offset(PolygonImpl& sh, TCoord distance) } } +template<> +inline void offset(PathImpl& sh, TCoord distance, const PathTag&) +{ + PolygonImpl p(std::move(sh)); + offset(p, distance, PolygonTag()); + sh = p.Contour; +} + // Tell libnest2d how to make string out of a ClipperPolygon object template<> inline std::string toString(const PolygonImpl& sh) { @@ -259,10 +268,12 @@ inline TMultiShape clipper_execute( poly.Contour.swap(pptr->Contour); assert(!pptr->IsHole()); - - if(pptr->IsOpen()) { + + if(!poly.Contour.empty() ) { auto front_p = poly.Contour.front(); - poly.Contour.emplace_back(front_p); + auto &back_p = poly.Contour.back(); + if(front_p.X != back_p.X || front_p.Y != back_p.X) + poly.Contour.emplace_back(front_p); } for(auto h : pptr->Childs) { processHole(h, poly); } @@ -274,10 +285,12 @@ inline TMultiShape clipper_execute( poly.Holes.emplace_back(std::move(pptr->Contour)); assert(pptr->IsHole()); - - if(pptr->IsOpen()) { - auto front_p = poly.Holes.back().front(); - poly.Holes.back().emplace_back(front_p); + + if(!poly.Contour.empty() ) { + auto front_p = poly.Contour.front(); + auto &back_p = poly.Contour.back(); + if(front_p.X != back_p.X || front_p.Y != back_p.X) + poly.Contour.emplace_back(front_p); } for(auto c : pptr->Childs) processPoly(c); diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 6c55d0e3f..827e2d8ba 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -30,11 +30,11 @@ template struct ShapeTag { using Type = typename Shape::Tag; }; template using Tag = typename ShapeTag>::Type; /// Meta function to derive the contour type for a polygon which could be itself -template struct ContourType { using Type = RawShape; }; +template struct ContourType { using Type = S; }; -/// TContour instead of `typename ContourType::type` -template -using TContour = typename ContourType>::Type; +/// TContour instead of `typename ContourType::type` +template +using TContour = typename ContourType>::Type; /// Getting the type of point structure used by a shape. template struct PointType { @@ -83,12 +83,12 @@ template struct ComputeType { template using TCompute = typename ComputeType>::Type; /// A meta function to derive a container type for holes in a polygon -template -struct HolesContainer { using Type = std::vector>; }; +template +struct HolesContainer { using Type = std::vector>; }; -/// Shorthand for `typename HolesContainer::Type` -template -using THolesContainer = typename HolesContainer>::Type; +/// Shorthand for `typename HolesContainer::Type` +template +using THolesContainer = typename HolesContainer>::Type; /* * TContour, TPoint, TCoord and TCompute should be usable for any type for which @@ -132,7 +132,7 @@ enum class Orientation { COUNTER_CLOCKWISE }; -template +template struct OrientationType { // Default Polygon orientation that the library expects @@ -146,68 +146,73 @@ template inline /*constexpr*/ bool is_clockwise() { /** * \brief A point pair base class for other point pairs (segment, box, ...). - * \tparam RawPoint The actual point type to use. + * \tparam P The actual point type to use. */ -template +template struct PointPair { - RawPoint p1; - RawPoint p2; + P p1; + P p2; }; /** * \brief An abstraction of a box; */ -template -class _Box: PointPair { - using PointPair::p1; - using PointPair::p2; +template +class _Box: PointPair

{ + using PointPair

::p1; + using PointPair

::p2; public: using Tag = BoxTag; - using PointType = RawPoint; + using PointType = P; - inline _Box() = default; - inline _Box(const RawPoint& p, const RawPoint& pp): - PointPair({p, pp}) {} + inline _Box(const P& center = {TCoord

(0), TCoord

(0)}): + _Box(TCoord

(0), TCoord

(0), center) {} + + inline _Box(const P& p, const P& pp): + PointPair

({p, pp}) {} + + inline _Box(TCoord

width, TCoord

height, + const P& p = {TCoord

(0), TCoord

(0)});/*: + _Box(p, P{width, height}) {}*/ - inline _Box(TCoord width, TCoord height): - _Box(RawPoint{0, 0}, RawPoint{width, height}) {} + inline const P& minCorner() const BP2D_NOEXCEPT { return p1; } + inline const P& maxCorner() const BP2D_NOEXCEPT { return p2; } - inline const RawPoint& minCorner() const BP2D_NOEXCEPT { return p1; } - inline const RawPoint& maxCorner() const BP2D_NOEXCEPT { return p2; } + inline P& minCorner() BP2D_NOEXCEPT { return p1; } + inline P& maxCorner() BP2D_NOEXCEPT { return p2; } - inline RawPoint& minCorner() BP2D_NOEXCEPT { return p1; } - inline RawPoint& maxCorner() BP2D_NOEXCEPT { return p2; } + inline TCoord

width() const BP2D_NOEXCEPT; + inline TCoord

height() const BP2D_NOEXCEPT; - inline TCoord width() const BP2D_NOEXCEPT; - inline TCoord height() const BP2D_NOEXCEPT; + inline P center() const BP2D_NOEXCEPT; - inline RawPoint center() const BP2D_NOEXCEPT; - - template> + template> inline Unit area() const BP2D_NOEXCEPT { return Unit(width())*height(); } + + static inline _Box infinite(const P ¢er = {TCoord

(0), TCoord

(0)}); }; template struct PointType<_Box> { using Type = typename _Box::PointType; }; -template +template class _Circle { - RawPoint center_; + P center_; double radius_ = 0; public: using Tag = CircleTag; - using PointType = RawPoint; + using PointType = P; _Circle() = default; - _Circle(const RawPoint& center, double r): center_(center), radius_(r) {} + _Circle(const P& center, double r): center_(center), radius_(r) {} - inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; } - inline void center(const RawPoint& c) { center_ = c; } + inline const P& center() const BP2D_NOEXCEPT { return center_; } + inline void center(const P& c) { center_ = c; } inline double radius() const BP2D_NOEXCEPT { return radius_; } inline void radius(double r) { radius_ = r; } @@ -224,38 +229,38 @@ template struct PointType<_Circle> { /** * \brief An abstraction of a directed line segment with two points. */ -template -class _Segment: PointPair { - using PointPair::p1; - using PointPair::p2; +template +class _Segment: PointPair

{ + using PointPair

::p1; + using PointPair

::p2; mutable Radians angletox_ = std::nan(""); public: - using PointType = RawPoint; + using PointType = P; inline _Segment() = default; - inline _Segment(const RawPoint& p, const RawPoint& pp): - PointPair({p, pp}) {} + inline _Segment(const P& p, const P& pp): + PointPair

({p, pp}) {} /** * @brief Get the first point. * @return Returns the starting point. */ - inline const RawPoint& first() const BP2D_NOEXCEPT { return p1; } + inline const P& first() const BP2D_NOEXCEPT { return p1; } /** * @brief The end point. * @return Returns the end point of the segment. */ - inline const RawPoint& second() const BP2D_NOEXCEPT { return p2; } + inline const P& second() const BP2D_NOEXCEPT { return p2; } - inline void first(const RawPoint& p) BP2D_NOEXCEPT + inline void first(const P& p) BP2D_NOEXCEPT { angletox_ = std::nan(""); p1 = p; } - inline void second(const RawPoint& p) BP2D_NOEXCEPT { + inline void second(const P& p) BP2D_NOEXCEPT { angletox_ = std::nan(""); p2 = p; } @@ -263,7 +268,7 @@ public: inline Radians angleToXaxis() const; /// The length of the segment in the measure of the coordinate system. - template> inline Unit sqlength() const; + template> inline Unit sqlength() const; }; @@ -275,42 +280,42 @@ template struct PointType<_Segment> { // used in friend declarations. namespace pointlike { -template -inline TCoord x(const RawPoint& p) +template +inline TCoord

x(const P& p) { return p.x(); } -template -inline TCoord y(const RawPoint& p) +template +inline TCoord

y(const P& p) { return p.y(); } -template -inline TCoord& x(RawPoint& p) +template +inline TCoord

& x(P& p) { return p.x(); } -template -inline TCoord& y(RawPoint& p) +template +inline TCoord

& y(P& p) { return p.y(); } -template> -inline Unit squaredDistance(const RawPoint& p1, const RawPoint& p2) +template> +inline Unit squaredDistance(const P& p1, const P& p2) { 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 -inline double distance(const RawPoint& p1, const RawPoint& p2) +template +inline double distance(const P& p1, const P& p2) { - return std::sqrt(squaredDistance(p1, p2)); + return std::sqrt(squaredDistance(p1, p2)); } // create perpendicular vector @@ -339,9 +344,9 @@ inline Unit magnsq(const Pt& p) return Unit(x(p)) * x(p) + Unit(y(p)) * y(p); } -template> +template> inline std::pair horizontalDistance( - const RawPoint& p, const _Segment& s) + const P& p, const _Segment

& s) { namespace pl = pointlike; auto x = Unit(pl::x(p)), y = Unit(pl::y(p)); @@ -364,9 +369,9 @@ inline std::pair horizontalDistance( return {ret, true}; } -template> +template> inline std::pair verticalDistance( - const RawPoint& p, const _Segment& s) + const P& p, const _Segment

& s) { namespace pl = pointlike; auto x = Unit(pl::x(p)), y = Unit(pl::y(p)); @@ -390,42 +395,42 @@ inline std::pair verticalDistance( } } -template -TCoord _Box::width() const BP2D_NOEXCEPT +template +TCoord

_Box

::width() const BP2D_NOEXCEPT { return pointlike::x(maxCorner()) - pointlike::x(minCorner()); } -template -TCoord _Box::height() const BP2D_NOEXCEPT +template +TCoord

_Box

::height() const BP2D_NOEXCEPT { return pointlike::y(maxCorner()) - pointlike::y(minCorner()); } -template -TCoord getX(const RawPoint& p) { return pointlike::x(p); } +template +TCoord

getX(const P& p) { return pointlike::x

(p); } -template -TCoord getY(const RawPoint& p) { return pointlike::y(p); } +template +TCoord

getY(const P& p) { return pointlike::y

(p); } -template -void setX(RawPoint& p, const TCoord& val) +template +void setX(P& p, const TCoord

& val) { - pointlike::x(p) = val; + pointlike::x

(p) = val; } -template -void setY(RawPoint& p, const TCoord& val) +template +void setY(P& p, const TCoord

& val) { - pointlike::y(p) = val; + pointlike::y

(p) = val; } -template -inline Radians _Segment::angleToXaxis() const +template +inline Radians _Segment

::angleToXaxis() const { if(std::isnan(static_cast(angletox_))) { - TCoord dx = getX(second()) - getX(first()); - TCoord dy = getY(second()) - getY(first()); + TCoord

dx = getX(second()) - getX(first()); + TCoord

dy = getY(second()) - getY(first()); double a = std::atan2(dy, dx); auto s = std::signbit(a); @@ -436,21 +441,55 @@ inline Radians _Segment::angleToXaxis() const return angletox_; } -template +template template -inline Unit _Segment::sqlength() const +inline Unit _Segment

::sqlength() const { - return pointlike::squaredDistance(first(), second()); + return pointlike::squaredDistance(first(), second()); } -template -inline RawPoint _Box::center() const BP2D_NOEXCEPT { +template +enable_if_t::value, T> modulo(const T &v, const T &m) +{ + return 0; +} +template +enable_if_t::value, T> modulo(const T &v, const T &m) +{ + return v % m; +} + +template +inline _Box

::_Box(TCoord

width, TCoord

height, const P & center) : + PointPair

({center - P{width / 2, height / 2}, + center + P{width / 2, height / 2} + + P{modulo(width, TCoord

(2)), + modulo(height, TCoord

(2))}}) {} + +template +inline _Box

_Box

::infinite(const P& center) { + using C = TCoord

; + _Box

ret; + + // It is important for Mx and My to be strictly less than half of the + // range of type C. width(), height() and area() will not overflow this way. + C Mx = C((std::numeric_limits::lowest() + 2 * getX(center)) / 2.01); + C My = C((std::numeric_limits::lowest() + 2 * getY(center)) / 2.01); + + ret.maxCorner() = center - P{Mx, My}; + ret.minCorner() = center + P{Mx, My}; + + return ret; +} + +template +inline P _Box

::center() const BP2D_NOEXCEPT { auto& minc = minCorner(); auto& maxc = maxCorner(); - using Coord = TCoord; + using Coord = TCoord

; - RawPoint ret = { // No rounding here, we dont know if these are int coords + P ret = { // No rounding here, we dont know if these are int coords Coord( (getX(minc) + getX(maxc)) / Coord(2) ), Coord( (getY(minc) + getY(maxc)) / Coord(2) ) }; @@ -467,76 +506,74 @@ enum class Formats { // used in friend declarations and can be aliased at class scope. namespace shapelike { -template -inline RawShape create(const TContour& contour, - const THolesContainer& holes) +template +inline S create(const TContour& contour, const THolesContainer& holes) { - return RawShape(contour, holes); + return S(contour, holes); } -template -inline RawShape create(TContour&& contour, - THolesContainer&& holes) +template +inline S create(TContour&& contour, THolesContainer&& holes) { - return RawShape(contour, holes); + return S(contour, holes); } -template -inline RawShape create(const TContour& contour) +template +inline S create(const TContour& contour) { - return create(contour, {}); + return create(contour, {}); } -template -inline RawShape create(TContour&& contour) +template +inline S create(TContour&& contour) { - return create(contour, {}); + return create(contour, {}); } -template -inline THolesContainer& holes(RawShape& /*sh*/) +template +inline THolesContainer& holes(S& /*sh*/) { - static THolesContainer empty; + static THolesContainer empty; return empty; } -template -inline const THolesContainer& holes(const RawShape& /*sh*/) +template +inline const THolesContainer& holes(const S& /*sh*/) { - static THolesContainer empty; + static THolesContainer empty; return empty; } -template -inline TContour& hole(RawShape& sh, unsigned long idx) +template +inline TContour& hole(S& sh, unsigned long idx) { return holes(sh)[idx]; } -template -inline const TContour& hole(const RawShape& sh, unsigned long idx) +template +inline const TContour& hole(const S& sh, unsigned long idx) { return holes(sh)[idx]; } -template -inline size_t holeCount(const RawShape& sh) +template +inline size_t holeCount(const S& sh) { return holes(sh).size(); } -template -inline TContour& contour(RawShape& sh) +template +inline TContour& contour(S& sh) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::contour() unimplemented!"); return sh; } -template -inline const TContour& contour(const RawShape& sh) +template +inline const TContour& contour(const S& sh) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::contour() unimplemented!"); return sh; } @@ -548,71 +585,71 @@ inline void reserve(RawPath& p, size_t vertex_capacity, const PathTag&) p.reserve(vertex_capacity); } -template -inline void addVertex(RawShape& sh, const PathTag&, Args...args) +template +inline void addVertex(S& sh, const PathTag&, Args...args) { sh.emplace_back(std::forward(args)...); } -template -inline void foreachVertex(RawShape& sh, Fn fn, const PathTag&) { +template +inline void foreachVertex(S& sh, Fn fn, const PathTag&) { std::for_each(sh.begin(), sh.end(), fn); } -template -inline typename RawShape::iterator begin(RawShape& sh, const PathTag&) +template +inline typename S::iterator begin(S& sh, const PathTag&) { return sh.begin(); } -template -inline typename RawShape::iterator end(RawShape& sh, const PathTag&) +template +inline typename S::iterator end(S& sh, const PathTag&) { return sh.end(); } -template -inline typename RawShape::const_iterator -cbegin(const RawShape& sh, const PathTag&) +template +inline typename S::const_iterator +cbegin(const S& sh, const PathTag&) { return sh.cbegin(); } -template -inline typename RawShape::const_iterator -cend(const RawShape& sh, const PathTag&) +template +inline typename S::const_iterator +cend(const S& sh, const PathTag&) { return sh.cend(); } -template -inline std::string toString(const RawShape& /*sh*/) +template +inline std::string toString(const S& /*sh*/) { return ""; } -template -inline std::string serialize(const RawShape& /*sh*/, double /*scale*/=1) +template +inline std::string serialize(const S& /*sh*/, double /*scale*/=1) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::serialize() unimplemented!"); return ""; } -template -inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/) +template +inline void unserialize(S& /*sh*/, const std::string& /*str*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::unserialize() unimplemented!"); } template inline Unit area(const Cntr& poly, const PathTag& ); -template -inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) +template +inline bool intersects(const S& /*sh*/, const S& /*sh*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::intersects() unimplemented!"); return false; } @@ -633,29 +670,29 @@ inline bool isInside(const TGuest&, const THost&, return false; } -template -inline bool touches( const RawShape& /*shape*/, - const RawShape& /*shape*/) +template +inline bool touches( const S& /*shape*/, + const S& /*shape*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::touches(shape, shape) unimplemented!"); return false; } -template -inline bool touches( const TPoint& /*point*/, - const RawShape& /*shape*/) +template +inline bool touches( const TPoint& /*point*/, + const S& /*shape*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::touches(point, shape) unimplemented!"); return false; } -template -inline _Box> boundingBox(const RawShape& /*sh*/, +template +inline _Box> boundingBox(const S& /*sh*/, const PathTag&) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::boundingBox(shape) unimplemented!"); } @@ -667,34 +704,41 @@ boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) "shapelike::boundingBox(shapes) unimplemented!"); } -template -inline RawShape convexHull(const RawShape& sh, const PathTag&); +template +inline S convexHull(const S& sh, const PathTag&); template inline S convexHull(const RawShapes& sh, const MultiPolygonTag&); -template -inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/) +template +inline void rotate(S& /*sh*/, const Radians& /*rads*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::rotate() unimplemented!"); } -template -inline void translate(RawShape& /*sh*/, const RawPoint& /*offs*/) +template +inline void translate(S& /*sh*/, const P& /*offs*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::translate() unimplemented!"); } -template -inline void offset(RawShape& /*sh*/, TCoord> /*distance*/) +template +inline void offset(S& /*sh*/, TCoord /*distance*/, const PathTag&) { dout() << "The current geometry backend does not support offsetting!\n"; } -template -inline std::pair isValid(const RawShape& /*sh*/) +template +inline void offset(S& sh, TCoord distance, const PolygonTag&) +{ + offset(contour(sh), distance); + for(auto &h : holes(sh)) offset(h, -distance); +} + +template +inline std::pair isValid(const S& /*sh*/) { return {false, "shapelike::isValid() unimplemented!"}; } @@ -735,56 +779,56 @@ template inline bool isConvex(const RawPath& sh, const PathTag&) // No need to implement these // ***************************************************************************** -template -inline typename TContour::iterator -begin(RawShape& sh, const PolygonTag&) +template +inline typename TContour::iterator +begin(S& sh, const PolygonTag&) { return begin(contour(sh), PathTag()); } -template // Tag dispatcher -inline auto begin(RawShape& sh) -> decltype(begin(sh, Tag())) +template // Tag dispatcher +inline auto begin(S& sh) -> decltype(begin(sh, Tag())) { - return begin(sh, Tag()); + return begin(sh, Tag()); } -template -inline typename TContour::const_iterator -cbegin(const RawShape& sh, const PolygonTag&) +template +inline typename TContour::const_iterator +cbegin(const S& sh, const PolygonTag&) { return cbegin(contour(sh), PathTag()); } -template // Tag dispatcher -inline auto cbegin(const RawShape& sh) -> decltype(cbegin(sh, Tag())) +template // Tag dispatcher +inline auto cbegin(const S& sh) -> decltype(cbegin(sh, Tag())) { - return cbegin(sh, Tag()); + return cbegin(sh, Tag()); } -template -inline typename TContour::iterator -end(RawShape& sh, const PolygonTag&) +template +inline typename TContour::iterator +end(S& sh, const PolygonTag&) { return end(contour(sh), PathTag()); } -template // Tag dispatcher -inline auto end(RawShape& sh) -> decltype(begin(sh, Tag())) +template // Tag dispatcher +inline auto end(S& sh) -> decltype(begin(sh, Tag())) { - return end(sh, Tag()); + return end(sh, Tag()); } -template -inline typename TContour::const_iterator -cend(const RawShape& sh, const PolygonTag&) +template +inline typename TContour::const_iterator +cend(const S& sh, const PolygonTag&) { return cend(contour(sh), PathTag()); } -template // Tag dispatcher -inline auto cend(const RawShape& sh) -> decltype(cend(sh, Tag())) +template // Tag dispatcher +inline auto cend(const S& sh) -> decltype(cend(sh, Tag())) { - return cend(sh, Tag()); + return cend(sh, Tag()); } template std::reverse_iterator _backward(It iter) { @@ -817,8 +861,8 @@ template TPoint

back (const P& p) { } // Optional, does nothing by default -template -inline void reserve(RawShape& sh, size_t vertex_capacity, const PolygonTag&) +template +inline void reserve(S& sh, size_t vertex_capacity, const PolygonTag&) { reserve(contour(sh), vertex_capacity, PathTag()); } @@ -828,20 +872,20 @@ inline void reserve(T& sh, size_t vertex_capacity) { reserve(sh, vertex_capacity, Tag()); } -template -inline void addVertex(RawShape& sh, const PolygonTag&, Args...args) +template +inline void addVertex(S& sh, const PolygonTag&, Args...args) { addVertex(contour(sh), PathTag(), std::forward(args)...); } -template // Tag dispatcher -inline void addVertex(RawShape& sh, Args...args) +template // Tag dispatcher +inline void addVertex(S& sh, Args...args) { - addVertex(sh, Tag(), std::forward(args)...); + addVertex(sh, Tag(), std::forward(args)...); } -template -inline _Box> boundingBox(const RawShape& poly, const PolygonTag&) +template +inline _Box> boundingBox(const S& poly, const PolygonTag&) { return boundingBox(contour(poly), PathTag()); } @@ -875,6 +919,28 @@ inline _Box> boundingBox(const S& sh) return boundingBox(sh, Tag() ); } +template _Box

boundingBox(const _Box

& bb1, const _Box

& bb2 ) +{ + auto& pminc = bb1.minCorner(); + auto& pmaxc = bb1.maxCorner(); + auto& iminc = bb2.minCorner(); + auto& imaxc = bb2.maxCorner(); + P minc, maxc; + + setX(minc, std::min(getX(pminc), getX(iminc))); + setY(minc, std::min(getY(pminc), getY(iminc))); + + setX(maxc, std::max(getX(pmaxc), getX(imaxc))); + setY(maxc, std::max(getY(pmaxc), getY(imaxc))); + return _Box

(minc, maxc); +} + +template +_Box> boundingBox(const S1 &s1, const S2 &s2) +{ + return boundingBox(boundingBox(s1), boundingBox(s2)); +} + template inline double area(const Box& box, const BoxTag& ) { @@ -916,40 +982,40 @@ template inline double area(const S& poly, const PolygonTag& ) }); } -template // Dispatching function -inline double area(const RawShape& sh) +template // Dispatching function +inline double area(const S& sh) { - return area(sh, Tag()); + return area(sh, Tag()); } template inline double area(const RawShapes& shapes, const MultiPolygonTag&) { - using RawShape = typename RawShapes::value_type; + using S = typename RawShapes::value_type; return std::accumulate(shapes.begin(), shapes.end(), 0.0, - [](double a, const RawShape& b) { + [](double a, const S& b) { return a += area(b); }); } -template -inline RawShape convexHull(const RawShape& sh, const PolygonTag&) +template +inline S convexHull(const S& sh, const PolygonTag&) { - return create(convexHull(contour(sh), PathTag())); + return create(convexHull(contour(sh), PathTag())); } -template -inline auto convexHull(const RawShape& sh) - -> decltype(convexHull(sh, Tag())) // TODO: C++14 could deduce +template +inline auto convexHull(const S& sh) + -> decltype(convexHull(sh, Tag())) // TODO: C++14 could deduce { - return convexHull(sh, Tag()); + return convexHull(sh, Tag()); } -template -inline RawShape convexHull(const RawShape& sh, const PathTag&) +template +inline S convexHull(const S& sh, const PathTag&) { - using Unit = TCompute; - using Point = TPoint; + using Unit = TCompute; + using Point = TPoint; namespace sl = shapelike; size_t edges = sl::cend(sh) - sl::cbegin(sh); @@ -994,8 +1060,8 @@ inline RawShape convexHull(const RawShape& sh, const PathTag&) ++ik; } - RawShape ret; reserve(ret, U.size() + L.size()); - if(is_clockwise()) { + S ret; reserve(ret, U.size() + L.size()); + if(is_clockwise()) { 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) @@ -1046,11 +1112,11 @@ inline bool isInside(const TP& point, const TB& box, return px > minx && px < maxx && py > miny && py < maxy; } -template -inline bool isInside(const RawShape& sh, const TC& circ, +template +inline bool isInside(const S& sh, const TC& circ, const PolygonTag&, const CircleTag&) { - return std::all_of(cbegin(sh), cend(sh), [&circ](const TPoint& p) + return std::all_of(cbegin(sh), cend(sh), [&circ](const TPoint& p) { return isInside(p, circ, PointTag(), CircleTag()); }); @@ -1060,8 +1126,8 @@ template inline bool isInside(const TB& box, const TC& circ, const BoxTag&, const CircleTag&) { - return isInside(box.minCorner(), circ, BoxTag(), CircleTag()) && - isInside(box.maxCorner(), circ, BoxTag(), CircleTag()); + return isInside(box.minCorner(), circ, PointTag(), CircleTag()) && + isInside(box.maxCorner(), circ, PointTag(), CircleTag()); } template @@ -1081,8 +1147,8 @@ inline bool isInside(const TBGuest& ibb, const TBHost& box, return iminX > minX && imaxX < maxX && iminY > minY && imaxY < maxY; } -template -inline bool isInside(const RawShape& poly, const TB& box, +template +inline bool isInside(const S& poly, const TB& box, const PolygonTag&, const BoxTag&) { return isInside(boundingBox(poly), box, BoxTag(), BoxTag()); @@ -1093,36 +1159,36 @@ inline bool isInside(const TGuest& guest, const THost& host) { return isInside(guest, host, Tag(), Tag()); } -template // Potential O(1) implementation may exist -inline TPoint& vertex(RawShape& sh, unsigned long idx, +template // Potential O(1) implementation may exist +inline TPoint& vertex(S& sh, unsigned long idx, const PolygonTag&) { return *(shapelike::begin(contour(sh)) + idx); } -template // Potential O(1) implementation may exist -inline TPoint& vertex(RawShape& sh, unsigned long idx, +template // Potential O(1) implementation may exist +inline TPoint& vertex(S& sh, unsigned long idx, const PathTag&) { return *(shapelike::begin(sh) + idx); } -template // Potential O(1) implementation may exist -inline TPoint& vertex(RawShape& sh, unsigned long idx) +template // Potential O(1) implementation may exist +inline TPoint& vertex(S& sh, unsigned long idx) { - return vertex(sh, idx, Tag()); + return vertex(sh, idx, Tag()); } -template // Potential O(1) implementation may exist -inline const TPoint& vertex(const RawShape& sh, +template // Potential O(1) implementation may exist +inline const TPoint& vertex(const S& sh, unsigned long idx, const PolygonTag&) { return *(shapelike::cbegin(contour(sh)) + idx); } -template // Potential O(1) implementation may exist -inline const TPoint& vertex(const RawShape& sh, +template // Potential O(1) implementation may exist +inline const TPoint& vertex(const S& sh, unsigned long idx, const PathTag&) { @@ -1130,28 +1196,28 @@ inline const TPoint& vertex(const RawShape& sh, } -template // Potential O(1) implementation may exist -inline const TPoint& vertex(const RawShape& sh, +template // Potential O(1) implementation may exist +inline const TPoint& vertex(const S& sh, unsigned long idx) { - return vertex(sh, idx, Tag()); + return vertex(sh, idx, Tag()); } -template -inline size_t contourVertexCount(const RawShape& sh) +template +inline size_t contourVertexCount(const S& sh) { return shapelike::cend(sh) - shapelike::cbegin(sh); } -template -inline void foreachVertex(RawShape& sh, Fn fn, const PolygonTag&) { +template +inline void foreachVertex(S& sh, Fn fn, const PolygonTag&) { foreachVertex(contour(sh), fn, PathTag()); for(auto& h : holes(sh)) foreachVertex(h, fn, PathTag()); } -template -inline void foreachVertex(RawShape& sh, Fn fn) { - foreachVertex(sh, fn, Tag()); +template +inline void foreachVertex(S& sh, Fn fn) { + foreachVertex(sh, fn, Tag()); } template inline bool isConvex(const Poly& sh, const PolygonTag&) @@ -1162,9 +1228,26 @@ template inline bool isConvex(const Poly& sh, const PolygonTag&) return convex; } -template inline bool isConvex(const RawShape& sh) // dispatch +template inline bool isConvex(const S& sh) // dispatch { - return isConvex(sh, Tag()); + return isConvex(sh, Tag()); +} + +template inline void offset(Box& bb, TCoord d, const BoxTag&) +{ + TPoint md{d, d}; + bb.minCorner() -= md; + bb.maxCorner() += md; +} + +template inline void offset(C& circ, TCoord d, const CircleTag&) +{ + circ.radius(circ.radius() + double(d)); +} + +// Dispatch function +template inline void offset(S& sh, TCoord d) { + offset(sh, d, Tag()); } } diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index 5d74aa3d9..29d52c10f 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -12,6 +12,8 @@ namespace libnest2d { +static const constexpr int BIN_ID_UNSET = -1; + /** * \brief An item to be placed on a bin. * @@ -34,22 +36,22 @@ class _Item { RawShape sh_; // Transformation data - Vertex translation_; - Radians rotation_; - Coord offset_distance_; + Vertex translation_{0, 0}; + Radians rotation_{0.0}; + Coord inflation_{0}; // Info about whether the transformations will have to take place // This is needed because if floating point is used, it is hard to say // that a zero angle is not a rotation because of testing for equality. - bool has_rotation_ = false, has_translation_ = false, has_offset_ = false; + bool has_rotation_ = false, has_translation_ = false, has_inflation_ = false; // For caching the calculations as they can get pretty expensive. mutable RawShape tr_cache_; mutable bool tr_cache_valid_ = false; mutable double area_cache_ = 0; mutable bool area_cache_valid_ = false; - mutable RawShape offset_cache_; - mutable bool offset_cache_valid_ = false; + mutable RawShape inflate_cache_; + mutable bool inflate_cache_valid_ = false; enum class Convexity: char { UNCHECKED, @@ -65,6 +67,9 @@ class _Item { Box bb; bool valid; BBCache(): valid(false) {} } bb_cache_; + + int binid_{BIN_ID_UNSET}, priority_{0}; + bool fixed_{false}; public: @@ -121,8 +126,16 @@ public: inline _Item(TContour&& contour, THolesContainer&& holes): - sh_(sl::create(std::move(contour), - std::move(holes))) {} + sh_(sl::create(std::move(contour), std::move(holes))) {} + + inline bool isFixed() const noexcept { return fixed_; } + inline void markAsFixed(bool fixed = true) { fixed_ = fixed; } + + inline void binId(int idx) { binid_ = idx; } + inline int binId() const noexcept { return binid_; } + + inline void priority(int p) { priority_ = p; } + inline int priority() const noexcept { return priority_; } /** * @brief Convert the polygon to string representation. The format depends @@ -200,7 +213,7 @@ public: double ret ; if(area_cache_valid_) ret = area_cache_; else { - ret = sl::area(offsettedShape()); + ret = sl::area(infaltedShape()); area_cache_ = ret; area_cache_valid_ = true; } @@ -271,17 +284,21 @@ public: { rotation(rotation() + rads); } - - inline void addOffset(Coord distance) BP2D_NOEXCEPT + + inline void inflation(Coord distance) BP2D_NOEXCEPT { - offset_distance_ = distance; - has_offset_ = true; + inflation_ = distance; + has_inflation_ = true; invalidateCache(); } - - inline void removeOffset() BP2D_NOEXCEPT { - has_offset_ = false; - invalidateCache(); + + inline Coord inflation() const BP2D_NOEXCEPT { + return inflation_; + } + + inline void inflate(Coord distance) BP2D_NOEXCEPT + { + inflation(inflation() + distance); } inline Radians rotation() const BP2D_NOEXCEPT @@ -315,7 +332,7 @@ public: { if(tr_cache_valid_) return tr_cache_; - RawShape cpy = offsettedShape(); + RawShape cpy = infaltedShape(); if(has_rotation_) sl::rotate(cpy, rotation_); if(has_translation_) sl::translate(cpy, translation_); tr_cache_ = cpy; tr_cache_valid_ = true; @@ -336,17 +353,17 @@ public: inline void resetTransformation() BP2D_NOEXCEPT { - has_translation_ = false; has_rotation_ = false; has_offset_ = false; + has_translation_ = false; has_rotation_ = false; has_inflation_ = false; invalidateCache(); } inline Box boundingBox() const { if(!bb_cache_.valid) { if(!has_rotation_) - bb_cache_.bb = sl::boundingBox(offsettedShape()); + bb_cache_.bb = sl::boundingBox(infaltedShape()); else { // TODO make sure this works - auto rotsh = offsettedShape(); + auto rotsh = infaltedShape(); sl::rotate(rotsh, rotation_); bb_cache_.bb = sl::boundingBox(rotsh); } @@ -395,14 +412,14 @@ public: private: - inline const RawShape& offsettedShape() const { - if(has_offset_ ) { - if(offset_cache_valid_) return offset_cache_; + inline const RawShape& infaltedShape() const { + if(has_inflation_ ) { + if(inflate_cache_valid_) return inflate_cache_; - offset_cache_ = sh_; - sl::offset(offset_cache_, offset_distance_); - offset_cache_valid_ = true; - return offset_cache_; + inflate_cache_ = sh_; + sl::offset(inflate_cache_, inflation_); + inflate_cache_valid_ = true; + return inflate_cache_; } return sh_; } @@ -412,7 +429,7 @@ private: tr_cache_valid_ = false; lmb_valid_ = false; rmt_valid_ = false; area_cache_valid_ = false; - offset_cache_valid_ = false; + inflate_cache_valid_ = false; bb_cache_.valid = false; convexity_ = Convexity::UNCHECKED; } @@ -492,24 +509,6 @@ template using _ItemGroup = std::vector<_ItemRef>; template using _PackGroup = std::vector>>; -/** - * \brief A list of packed (index, item) pair vectors. Each vector represents a - * bin. - * - * The index is points to the position of the item in the original input - * sequence. This way the caller can use the items as a transformation data - * carrier and transform the original objects manually. - */ -template -using _IndexedPackGroup = std::vector< - std::vector< - std::pair< - unsigned, - _ItemRef - > - > - >; - template struct ConstItemRange { Iterator from; @@ -738,54 +737,45 @@ public: return impl_.getResult(); } - /** - * @brief Loading a group of already packed bins. It is best to use a result - * from a previous packing. The algorithm will consider this input as if the - * objects are already packed and not move them. If any of these items are - * outside the bin, it is up to the placer algorithm what will happen. - * Packing additional items can fail for the bottom-left and nfp placers. - * @param pckgrp A packgroup which is a vector of item vectors. Each item - * vector corresponds to a packed bin. - */ - inline void preload(const PackGroup& pckgrp) { impl_.preload(pckgrp); } - void clear() { impl_.clear(); } }; /** - * The Arranger is the front-end class for the libnest2d library. It takes the - * input items and outputs the items with the proper transformations to be - * inside the provided bin. + * The _Nester is the front-end class for the libnest2d library. It takes the + * input items and changes their transformations to be inside the provided bin. */ template -class Nester { +class _Nester { using TSel = SelectionStrategyLike; TSel selector_; public: using Item = typename PlacementStrategy::Item; + using ShapeType = typename Item::ShapeType; using ItemRef = std::reference_wrapper; using TPlacer = PlacementStrategyLike; using BinType = typename TPlacer::BinType; using PlacementConfig = typename TPlacer::Config; using SelectionConfig = typename TSel::Config; - - using Unit = TCoord>; - - using IndexedPackGroup = _IndexedPackGroup; + using Coord = TCoord>; using PackGroup = _PackGroup; using ResultType = PackGroup; - using ResultTypeIndexed = IndexedPackGroup; private: BinType bin_; PlacementConfig pconfig_; - Unit min_obj_distance_; + Coord min_obj_distance_; using SItem = typename SelectionStrategy::Item; using TPItem = remove_cvref_t; using TSItem = remove_cvref_t; - std::vector item_cache_; + StopCondition stopfn_; + + template using TVal = remove_ref_t; + + template + using ItemIteratorOnly = + enable_if_t&, TPItem&>::value, Out>; public: @@ -798,10 +788,8 @@ public: template - Nester( TBinType&& bin, - Unit min_obj_distance = 0, - const PConf& pconfig = PConf(), - const SConf& sconfig = SConf()): + _Nester(TBinType&& bin, Coord min_obj_distance = 0, + const PConf& pconfig = PConf(), const SConf& sconfig = SConf()): bin_(std::forward(bin)), pconfig_(pconfig), min_obj_distance_(min_obj_distance) @@ -814,182 +802,59 @@ public: void configure(const PlacementConfig& pconf) { pconfig_ = pconf; } void configure(const SelectionConfig& sconf) { selector_.configure(sconf); } - void configure(const PlacementConfig& pconf, const SelectionConfig& sconf) { + void configure(const PlacementConfig& pconf, const SelectionConfig& sconf) + { pconfig_ = pconf; selector_.configure(sconf); } - void configure(const SelectionConfig& sconf, const PlacementConfig& pconf) { + void configure(const SelectionConfig& sconf, const PlacementConfig& pconf) + { pconfig_ = pconf; selector_.configure(sconf); } /** - * \brief Arrange an input sequence and return a PackGroup object with - * the packed groups corresponding to the bins. + * \brief Arrange an input sequence of _Item-s. + * + * To get the result, call the translation(), rotation() and binId() + * methods of each item. If only the transformed polygon is needed, call + * transformedShape() to get the properly transformed shapes. * * The number of groups in the pack group is the number of bins opened by * the selection algorithm. */ - template - inline PackGroup execute(TIterator from, TIterator to) + template + inline ItemIteratorOnly execute(It from, It to) { - return _execute(from, to); - } - - /** - * A version of the arrange method returning an IndexedPackGroup with - * the item indexes into the original input sequence. - * - * Takes a little longer to collect the indices. Scales linearly with the - * input sequence size. - */ - template - inline IndexedPackGroup executeIndexed(TIterator from, TIterator to) - { - return _executeIndexed(from, to); - } - - /// Shorthand to normal arrange method. - template - inline PackGroup operator() (TIterator from, TIterator to) - { - return _execute(from, to); + auto infl = static_cast(std::ceil(min_obj_distance_/2.0)); + if(infl > 0) std::for_each(from, to, [this, infl](Item& item) { + item.inflate(infl); + }); + + selector_.template packItems( + from, to, bin_, pconfig_); + + if(min_obj_distance_ > 0) std::for_each(from, to, [infl](Item& item) { + item.inflate(-infl); + }); } /// Set a progress indicator function object for the selector. - inline Nester& progressIndicator(ProgressFunction func) + inline _Nester& progressIndicator(ProgressFunction func) { selector_.progressIndicator(func); return *this; } /// Set a predicate to tell when to abort nesting. - inline Nester& stopCondition(StopCondition fn) + inline _Nester& stopCondition(StopCondition fn) { - selector_.stopCondition(fn); return *this; + stopfn_ = fn; selector_.stopCondition(fn); return *this; } inline const PackGroup& lastResult() const { return selector_.getResult(); } - - inline void preload(const PackGroup& pgrp) - { - selector_.preload(pgrp); - } - - inline void preload(const IndexedPackGroup& ipgrp) - { - PackGroup pgrp; pgrp.reserve(ipgrp.size()); - for(auto& ig : ipgrp) { - pgrp.emplace_back(); pgrp.back().reserve(ig.size()); - for(auto& r : ig) pgrp.back().emplace_back(r.second); - } - preload(pgrp); - } - -private: - - template, - - // This function will be used only if the iterators are pointing to - // a type compatible with the libnets2d::_Item template. - // This way we can use references to input elements as they will - // have to exist for the lifetime of this call. - class T = enable_if_t< std::is_convertible::value, IT> - > - inline const PackGroup& _execute(TIterator from, TIterator to, bool = false) - { - __execute(from, to); - return lastResult(); - } - - template, - class T = enable_if_t::value, IT> - > - inline const PackGroup& _execute(TIterator from, TIterator to, int = false) - { - item_cache_ = {from, to}; - - __execute(item_cache_.begin(), item_cache_.end()); - return lastResult(); - } - - template, - - // This function will be used only if the iterators are pointing to - // a type compatible with the libnest2d::_Item template. - // This way we can use references to input elements as they will - // have to exist for the lifetime of this call. - class T = enable_if_t< std::is_convertible::value, IT> - > - inline IndexedPackGroup _executeIndexed(TIterator from, - TIterator to, - bool = false) - { - __execute(from, to); - return createIndexedPackGroup(from, to, selector_); - } - - template, - class T = enable_if_t::value, IT> - > - inline IndexedPackGroup _executeIndexed(TIterator from, - TIterator to, - int = false) - { - item_cache_ = {from, to}; - __execute(item_cache_.begin(), item_cache_.end()); - return createIndexedPackGroup(from, to, selector_); - } - - template - static IndexedPackGroup createIndexedPackGroup(TIterator from, - TIterator to, - TSel& selector) - { - IndexedPackGroup pg; - pg.reserve(selector.getResult().size()); - - const PackGroup& pckgrp = selector.getResult(); - - for(size_t i = 0; i < pckgrp.size(); i++) { - auto items = pckgrp[i]; - pg.emplace_back(); - pg[i].reserve(items.size()); - - for(Item& itemA : items) { - auto it = from; - unsigned idx = 0; - while(it != to) { - Item& itemB = *it; - if(&itemB == &itemA) break; - it++; idx++; - } - pg[i].emplace_back(idx, itemA); - } - } - - return pg; - } - - template inline void __execute(TIter from, TIter to) - { - if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { - item.addOffset(static_cast(std::ceil(min_obj_distance_/2.0))); - }); - - selector_.template packItems( - from, to, bin_, pconfig_); - - if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) { - item.removeOffset(); - }); - } }; } diff --git a/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp b/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp index 28aaad5ce..e1a4ffbd9 100644 --- a/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp @@ -68,11 +68,11 @@ public: return toWallPoly(item, Dir::DOWN); } - inline Unit availableSpaceLeft(const Item& item) { + inline Coord availableSpaceLeft(const Item& item) { return availableSpace(item, Dir::LEFT); } - inline Unit availableSpaceDown(const Item& item) { + inline Coord availableSpaceDown(const Item& item) { return availableSpace(item, Dir::DOWN); } @@ -83,7 +83,7 @@ protected: // Get initial position for item in the top right corner setInitialPosition(item); - Unit d = availableSpaceDown(item); + Coord d = availableSpaceDown(item); auto eps = config_.epsilon; bool can_move = d > eps; bool can_be_packed = can_move; @@ -179,7 +179,7 @@ protected: return ret; } - Unit availableSpace(const Item& _item, const Dir dir) { + Coord availableSpace(const Item& _item, const Dir dir) { Item item (_item.transformedShape()); @@ -223,7 +223,7 @@ protected: cmp); // Get the initial distance in floating point - Unit m = getCoord(*minvertex_it); + Coord m = getCoord(*minvertex_it); // Check available distance for every vertex of item to the objects // in the way for the nearest intersection diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index c1f15fe61..4ef1fe71d 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -581,8 +581,12 @@ public: static inline double overfit(const Box& bb, const Box& bin) { - auto wdiff = double(bb.width() - bin.width()); - auto hdiff = double(bb.height() - bin.height()); + auto Bw = bin.width(); + auto Bh = bin.height(); + auto mBw = -Bw; + auto mBh = -Bh; + auto wdiff = double(bb.width()) + mBw; + auto hdiff = double(bb.height()) + mBh; double diff = 0; if(wdiff > 0) diff += wdiff; if(hdiff > 0) diff += hdiff; @@ -801,7 +805,6 @@ private: // optimize config_.object_function = prev_func; } - } struct Optimum { @@ -816,29 +819,14 @@ private: class Optimizer: public opt::TOptimizer { public: - Optimizer() { + Optimizer(float accuracy = 1.f) { opt::StopCriteria stopcr; - stopcr.max_iterations = 200; + stopcr.max_iterations = unsigned(std::floor(1000 * accuracy)); stopcr.relative_score_difference = 1e-20; this->stopcr_ = stopcr; } }; - static Box boundingBox(const Box& pilebb, const Box& ibb ) { - auto& pminc = pilebb.minCorner(); - auto& pmaxc = pilebb.maxCorner(); - auto& iminc = ibb.minCorner(); - auto& imaxc = ibb.maxCorner(); - Vertex minc, maxc; - - setX(minc, std::min(getX(pminc), getX(iminc))); - setY(minc, std::min(getY(pminc), getY(iminc))); - - setX(maxc, std::max(getX(pmaxc), getX(imaxc))); - setY(maxc, std::max(getY(pmaxc), getY(imaxc))); - return Box(minc, maxc); - } - using Edges = EdgeCache; template> @@ -935,7 +923,7 @@ private: _objfunc = [norm, binbb, pbb, ins_check](const Item& item) { auto ibb = item.boundingBox(); - auto fullbb = boundingBox(pbb, ibb); + auto fullbb = sl::boundingBox(pbb, ibb); double score = pl::distance(ibb.center(), binbb.center()); @@ -1005,14 +993,15 @@ private: auto& rofn = rawobjfunc; auto& nfpoint = getNfpPoint; + float accuracy = config_.accuracy; __parallel::enumerate( cache.corners().begin(), cache.corners().end(), - [&results, &item, &rofn, &nfpoint, ch] + [&results, &item, &rofn, &nfpoint, ch, accuracy] (double pos, size_t n) { - Optimizer solver; + Optimizer solver(accuracy); Item itemcpy = item; auto contour_ofn = [&rofn, &nfpoint, ch, &itemcpy] @@ -1059,10 +1048,10 @@ private: __parallel::enumerate(cache.corners(hidx).begin(), cache.corners(hidx).end(), [&results, &item, &nfpoint, - &rofn, ch, hidx] + &rofn, ch, hidx, accuracy] (double pos, size_t n) { - Optimizer solver; + Optimizer solver(accuracy); Item itmcpy = item; auto hole_ofn = diff --git a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp index abcd86183..26681aeec 100644 --- a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp @@ -18,7 +18,6 @@ public: using Segment = _Segment; using BinType = TBin; using Coord = TCoord; - using Unit = Coord; using Config = Cfg; using ItemGroup = _ItemGroup; using DefaultIter = typename ItemGroup::const_iterator; @@ -131,7 +130,6 @@ using typename Base::Vertex; \ using typename Base::Segment; \ using typename Base::PackResult; \ using typename Base::Coord; \ -using typename Base::Unit; \ private: } diff --git a/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp b/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp index 25007e580..f904210aa 100644 --- a/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp +++ b/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp @@ -711,7 +711,12 @@ public: addBin(); packjob(placers[idx], remaining, idx); idx++; } - + + int binid = 0; + for(auto &bin : packed_bins_) { + for(Item& itm : bin) itm.binId(binid); + binid++; + } } }; diff --git a/src/libnest2d/include/libnest2d/selections/firstfit.hpp b/src/libnest2d/include/libnest2d/selections/firstfit.hpp index d521673b4..5585e565d 100644 --- a/src/libnest2d/include/libnest2d/selections/firstfit.hpp +++ b/src/libnest2d/include/libnest2d/selections/firstfit.hpp @@ -39,6 +39,20 @@ public: std::vector placers; placers.reserve(last-first); + + std::for_each(first, last, [this](Item& itm) { + if(itm.isFixed()) { + if (itm.binId() < 0) itm.binId(0); + auto binidx = size_t(itm.binId()); + + while(packed_bins_.size() <= binidx) + packed_bins_.emplace_back(); + + packed_bins_[binidx].emplace_back(itm); + } else { + store_.emplace_back(itm); + } + }); // If the packed_items array is not empty we have to create as many // placers as there are elements in packed bins and preload each item @@ -48,11 +62,10 @@ public: placers.back().configure(pconfig); placers.back().preload(ig); } - - std::copy(first, last, std::back_inserter(store_)); - + auto sortfunc = [](Item& i1, Item& i2) { - return i1.area() > i2.area(); + int p1 = i1.priority(), p2 = i2.priority(); + return p1 == p2 ? i1.area() > i2.area() : p1 > p2; }; std::sort(store_.begin(), store_.end(), sortfunc); @@ -76,7 +89,6 @@ public: } } - auto it = store_.begin(); while(it != store_.end() && !cancelled()) { @@ -84,8 +96,10 @@ public: size_t j = 0; while(!was_packed && !cancelled()) { for(; j < placers.size() && !was_packed && !cancelled(); j++) { - if((was_packed = placers[j].pack(*it, rem(it, store_) ))) - makeProgress(placers[j], j); + if((was_packed = placers[j].pack(*it, rem(it, store_) ))) { + it->get().binId(int(j)); + makeProgress(placers[j], j); + } } if(!was_packed) { diff --git a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp index fd6577d97..9ae92fe29 100644 --- a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp @@ -22,8 +22,6 @@ public: inline void stopCondition(StopCondition cond) { stopcond_ = cond; } - inline void preload(const PackGroup& pckgrp) { packed_bins_ = pckgrp; } - inline void clear() { packed_bins_.clear(); } protected: diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index 2f2b9beb5..29577344d 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -363,29 +363,43 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) {5, 5}, {20, 20} }; + Box bin(210, 250, {105, 125}); - Nester arrange(Box(210, 250)); + ASSERT_EQ(bin.width(), 210); + ASSERT_EQ(bin.height(), 250); + ASSERT_EQ(getX(bin.center()), 105); + ASSERT_EQ(getY(bin.center()), 125); - auto groups = arrange(rects.begin(), rects.end()); - - ASSERT_EQ(groups.size(), 1u); - ASSERT_EQ(groups[0].size(), rects.size()); + _Nester arrange(bin); + arrange.execute(rects.begin(), rects.end()); + + auto max_group = std::max_element(rects.begin(), rects.end(), + [](const Item &i1, const Item &i2) { + return i1.binId() < i2.binId(); + }); + + int groups = max_group == rects.end() ? 0 : max_group->binId() + 1; + + ASSERT_EQ(groups, 1u); + ASSERT_TRUE( + std::all_of(rects.begin(), rects.end(), [](const Rectangle &itm) { + return itm.binId() != BIN_ID_UNSET; + })); + // check for no intersections, no containment: - for(auto result : groups) { - bool valid = true; - for(Item& r1 : result) { - for(Item& r2 : result) { - if(&r1 != &r2 ) { - valid = !Item::intersects(r1, r2) || Item::touches(r1, r2); - valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); - ASSERT_TRUE(valid); - } + bool valid = true; + for(Item& r1 : rects) { + for(Item& r2 : rects) { + if(&r1 != &r2 ) { + valid = !Item::intersects(r1, r2) || Item::touches(r1, r2); + ASSERT_TRUE(valid); + valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); + ASSERT_TRUE(valid); } } } - } TEST(GeometryAlgorithms, ArrangeRectanglesLoose) @@ -415,21 +429,36 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) {5, 5}, {20, 20} }; + Box bin(210, 250, {105, 125}); + + ASSERT_EQ(bin.width(), 210); + ASSERT_EQ(bin.height(), 250); + ASSERT_EQ(getX(bin.center()), 105); + ASSERT_EQ(getY(bin.center()), 125); + Coord min_obj_distance = 5; - Nester arrange(Box(210, 250), - min_obj_distance); + _Nester arrange(bin, min_obj_distance); - auto groups = arrange(rects.begin(), rects.end()); + arrange.execute(rects.begin(), rects.end()); - ASSERT_EQ(groups.size(), 1u); - ASSERT_EQ(groups[0].size(), rects.size()); + auto max_group = std::max_element(rects.begin(), rects.end(), + [](const Item &i1, const Item &i2) { + return i1.binId() < i2.binId(); + }); + + size_t groups = max_group == rects.end() ? 0 : max_group->binId() + 1; + + ASSERT_EQ(groups, 1u); + ASSERT_TRUE( + std::all_of(rects.begin(), rects.end(), [](const Rectangle &itm) { + return itm.binId() != BIN_ID_UNSET; + })); // check for no intersections, no containment: - auto result = groups[0]; bool valid = true; - for(Item& r1 : result) { - for(Item& r2 : result) { + for(Item& r1 : rects) { + for(Item& r2 : rects) { if(&r1 != &r2 ) { valid = !Item::intersects(r1, r2); valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); @@ -541,27 +570,24 @@ TEST(GeometryAlgorithms, convexHull) { TEST(GeometryAlgorithms, NestTest) { std::vector input = prusaParts(); + + libnest2d::nest(input, Box(250000000, 210000000), [](unsigned cnt) { + std::cout << "parts left: " << cnt << std::endl; + }); + + auto max_binid_it = std::max_element(input.begin(), input.end(), + [](const Item &i1, const Item &i2) { + return i1.binId() < i2.binId(); + }); + + size_t bins = max_binid_it == input.end() ? 0 : max_binid_it->binId() + 1; - PackGroup result = libnest2d::nest(input, - Box(250000000, 210000000), - [](unsigned cnt) { - std::cout - << "parts left: " << cnt - << std::endl; - }); - - ASSERT_LE(result.size(), 2); - - size_t partsum = std::accumulate(result.begin(), - result.end(), - size_t(0), - [](size_t s, - const decltype( - result)::value_type &bin) { - return s += bin.size(); - }); - - ASSERT_EQ(input.size(), partsum); + ASSERT_EQ(bins, 2u); + + ASSERT_TRUE( + std::all_of(input.begin(), input.end(), [](const Item &itm) { + return itm.binId() != BIN_ID_UNSET; + })); } namespace { diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp new file mode 100644 index 000000000..b4cfac954 --- /dev/null +++ b/src/libslic3r/Arrange.cpp @@ -0,0 +1,642 @@ +#include "Arrange.hpp" +#include "Geometry.hpp" +#include "SVG.hpp" +#include "MTUtils.hpp" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace libnest2d { +#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__) +using LargeInt = __int128; +#else +using LargeInt = boost::multiprecision::int128_t; +template<> struct _NumTag +{ + using Type = ScalarTag; +}; +#endif + +template struct _NumTag> +{ + using Type = RationalTag; +}; + +namespace nfp { + +template struct NfpImpl +{ + NfpResult operator()(const S &sh, const S &other) + { + return nfpConvexOnly>(sh, other); + } +}; + +} // namespace nfp +} // namespace libnest2d + +namespace Slic3r { + +template, int...EigenArgs> +inline SLIC3R_CONSTEXPR Eigen::Matrix unscaled( + const ClipperLib::IntPoint &v) SLIC3R_NOEXCEPT +{ + return Eigen::Matrix{unscaled(v.X), + unscaled(v.Y)}; +} + +namespace arrangement { + +using namespace libnest2d; +namespace clppr = ClipperLib; + +// Get the libnest2d types for clipper backend +using Item = _Item; +using Box = _Box; +using Circle = _Circle; +using Segment = _Segment; +using MultiPolygon = TMultiShape; + +// Summon the spatial indexing facilities from boost +namespace bgi = boost::geometry::index; +using SpatElement = std::pair; +using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; +using ItemGroup = std::vector>; + +// A coefficient used in separating bigger items and smaller items. +const double BIG_ITEM_TRESHOLD = 0.02; + +// Fill in the placer algorithm configuration with values carefully chosen for +// Slic3r. +template +void fillConfig(PConf& pcfg) { + + // Align the arranged pile into the center of the bin + pcfg.alignment = PConf::Alignment::CENTER; + + // Start placing the items from the center of the print bed + pcfg.starting_point = PConf::Alignment::CENTER; + + // TODO cannot use rotations until multiple objects of same geometry can + // handle different rotations. + pcfg.rotations = { 0.0 }; + + // The accuracy of optimization. + // Goes from 0.0 to 1.0 and scales performance as well + pcfg.accuracy = 0.65f; + + // Allow parallel execution. + pcfg.parallel = true; +} + +// Apply penalty to object function result. This is used only when alignment +// after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN) +double fixed_overfit(const std::tuple& result, const Box &binbb) +{ + double score = std::get<0>(result); + Box pilebb = std::get<1>(result); + Box fullbb = sl::boundingBox(pilebb, binbb); + auto diff = double(fullbb.area()) - binbb.area(); + if(diff > 0) score += diff; + + return score; +} + +// A class encapsulating the libnest2d Nester class and extending it with other +// management and spatial index structures for acceleration. +template +class AutoArranger { +public: + // Useful type shortcuts... + using Placer = typename placers::_NofitPolyPlacer; + using Selector = selections::_FirstFitSelection; + using Packer = _Nester; + using PConfig = typename Packer::PlacementConfig; + using Distance = TCoord; + +protected: + Packer m_pck; + PConfig m_pconf; // Placement configuration + TBin m_bin; + double m_bin_area; + SpatIndex m_rtree; // spatial index for the normal (bigger) objects + SpatIndex m_smallsrtree; // spatial index for only the smaller items + double m_norm; // A coefficient to scale distances + MultiPolygon m_merged_pile; // The already merged pile (vector of items) + Box m_pilebb; // The bounding box of the merged pile. + ItemGroup m_remaining; // Remaining items (m_items at the beginning) + ItemGroup m_items; // The items to be packed + + template ArithmeticOnly norm(T val) + { + return double(val) / m_norm; + } + + // This is "the" object function which is evaluated many times for each + // vertex (decimated with the accuracy parameter) of each object. + // Therefore it is upmost crucial for this function to be as efficient + // as it possibly can be but at the same time, it has to provide + // reasonable results. + std::tuple + objfunc(const Item &item, const clppr::IntPoint &bincenter) + { + const double bin_area = m_bin_area; + const SpatIndex& spatindex = m_rtree; + const SpatIndex& smalls_spatindex = m_smallsrtree; + const ItemGroup& remaining = m_remaining; + + // We will treat big items (compared to the print bed) differently + auto isBig = [bin_area](double a) { + return a/bin_area > BIG_ITEM_TRESHOLD ; + }; + + // Candidate item bounding box + auto ibb = item.boundingBox(); + + // Calculate the full bounding box of the pile with the candidate item + auto fullbb = sl::boundingBox(m_pilebb, ibb); + + // The bounding box of the big items (they will accumulate in the center + // of the pile + Box bigbb; + if(spatindex.empty()) bigbb = fullbb; + else { + auto boostbb = spatindex.bounds(); + boost::geometry::convert(boostbb, bigbb); + } + + // Will hold the resulting score + double score = 0; + + // Density is the pack density: how big is the arranged pile + double density = 0; + + // Distinction of cases for the arrangement scene + enum e_cases { + // This branch is for big items in a mixed (big and small) scene + // OR for all items in a small-only scene. + BIG_ITEM, + + // This branch is for the last big item in a mixed scene + LAST_BIG_ITEM, + + // For small items in a mixed scene. + SMALL_ITEM + } compute_case; + + bool bigitems = isBig(item.area()) || spatindex.empty(); + if(bigitems && !remaining.empty()) compute_case = BIG_ITEM; + else if (bigitems && remaining.empty()) compute_case = LAST_BIG_ITEM; + else compute_case = SMALL_ITEM; + + switch (compute_case) { + case BIG_ITEM: { + const clppr::IntPoint& minc = ibb.minCorner(); // bottom left corner + const clppr::IntPoint& maxc = ibb.maxCorner(); // top right corner + + // top left and bottom right corners + clppr::IntPoint top_left{getX(minc), getY(maxc)}; + clppr::IntPoint bottom_right{getX(maxc), getY(minc)}; + + // Now the distance of the gravity center will be calculated to the + // five anchor points and the smallest will be chosen. + std::array dists; + auto cc = fullbb.center(); // The gravity center + dists[0] = pl::distance(minc, cc); + dists[1] = pl::distance(maxc, cc); + dists[2] = pl::distance(ibb.center(), cc); + dists[3] = pl::distance(top_left, cc); + dists[4] = pl::distance(bottom_right, cc); + + // The smalles distance from the arranged pile center: + double dist = norm(*(std::min_element(dists.begin(), dists.end()))); + double bindist = norm(pl::distance(ibb.center(), bincenter)); + dist = 0.8 * dist + 0.2*bindist; + + // Prepare a variable for the alignment score. + // This will indicate: how well is the candidate item + // aligned with its neighbors. We will check the alignment + // with all neighbors and return the score for the best + // alignment. So it is enough for the candidate to be + // aligned with only one item. + auto alignment_score = 1.0; + + auto query = bgi::intersects(ibb); + auto& index = isBig(item.area()) ? spatindex : smalls_spatindex; + + // Query the spatial index for the neighbors + std::vector result; + result.reserve(index.size()); + + index.query(query, std::back_inserter(result)); + + // now get the score for the best alignment + for(auto& e : result) { + auto idx = e.second; + Item& p = m_items[idx]; + auto parea = p.area(); + if(std::abs(1.0 - parea/item.area()) < 1e-6) { + auto bb = sl::boundingBox(p.boundingBox(), ibb); + auto bbarea = bb.area(); + auto ascore = 1.0 - (item.area() + parea)/bbarea; + + if(ascore < alignment_score) alignment_score = ascore; + } + } + + density = std::sqrt(norm(fullbb.width()) * norm(fullbb.height())); + + // The final mix of the score is the balance between the + // distance from the full pile center, the pack density and + // the alignment with the neighbors + if (result.empty()) + score = 0.5 * dist + 0.5 * density; + else + score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; + + break; + } + case LAST_BIG_ITEM: { + auto mp = m_merged_pile; + mp.emplace_back(item.transformedShape()); + auto chull = sl::convexHull(mp); + + placers::EdgeCache ec(chull); + + double circ = norm(ec.circumference()); + double bcirc = 2.0 * norm(fullbb.width() + fullbb.height()); + score = 0.5 * circ + 0.5 * bcirc; + break; + } + case SMALL_ITEM: { + // Here there are the small items that should be placed around the + // already processed bigger items. + // No need to play around with the anchor points, the center will be + // just fine for small items + score = norm(pl::distance(ibb.center(), bigbb.center())); + break; + } + } + + return std::make_tuple(score, fullbb); + } + + std::function get_objfn(); + +public: + AutoArranger(const TBin & bin, + Distance dist, + std::function progressind, + std::function stopcond) + : m_pck(bin, dist) + , m_bin(bin) + , m_bin_area(sl::area(bin)) + , m_norm(std::sqrt(m_bin_area)) + { + fillConfig(m_pconf); + + // Set up a callback that is called just before arranging starts + // This functionality is provided by the Nester class (m_pack). + m_pconf.before_packing = + [this](const MultiPolygon& merged_pile, // merged pile + const ItemGroup& items, // packed items + const ItemGroup& remaining) // future items to be packed + { + m_items = items; + m_merged_pile = merged_pile; + m_remaining = remaining; + + m_pilebb = sl::boundingBox(merged_pile); + + m_rtree.clear(); + m_smallsrtree.clear(); + + // We will treat big items (compared to the print bed) differently + auto isBig = [this](double a) { + return a / m_bin_area > BIG_ITEM_TRESHOLD ; + }; + + for(unsigned idx = 0; idx < items.size(); ++idx) { + Item& itm = items[idx]; + if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx}); + m_smallsrtree.insert({itm.boundingBox(), idx}); + } + }; + + m_pconf.object_function = get_objfn(); + + if (progressind) m_pck.progressIndicator(progressind); + if (stopcond) m_pck.stopCondition(stopcond); + + m_pck.configure(m_pconf); + } + + template inline void operator()(Args&&...args) { + m_rtree.clear(); /*m_preload_idx.clear();*/ + m_pck.execute(std::forward(args)...); + } + + inline void preload(std::vector& fixeditems) { + m_pconf.alignment = PConfig::Alignment::DONT_ALIGN; + auto bb = sl::boundingBox(m_bin); + auto bbcenter = bb.center(); + m_pconf.object_function = [this, bb, bbcenter](const Item &item) { + return fixed_overfit(objfunc(item, bbcenter), bb); + }; + + // Build the rtree for queries to work + + for(unsigned idx = 0; idx < fixeditems.size(); ++idx) { + Item& itm = fixeditems[idx]; + itm.markAsFixed(); + } + + m_pck.configure(m_pconf); + } +}; + +template<> std::function AutoArranger::get_objfn() +{ + auto bincenter = m_bin.center(); + + return [this, bincenter](const Item &itm) { + auto result = objfunc(itm, bincenter); + + double score = std::get<0>(result); + auto& fullbb = std::get<1>(result); + + double miss = Placer::overfit(fullbb, m_bin); + miss = miss > 0? miss : 0; + score += miss*miss; + + return score; + }; +} + +template<> std::function AutoArranger::get_objfn() +{ + auto bincenter = m_bin.center(); + return [this, bincenter](const Item &item) { + + auto result = objfunc(item, bincenter); + + double score = std::get<0>(result); + + auto isBig = [this](const Item& itm) { + return itm.area() / m_bin_area > BIG_ITEM_TRESHOLD ; + }; + + if(isBig(item)) { + auto mp = m_merged_pile; + mp.push_back(item.transformedShape()); + auto chull = sl::convexHull(mp); + double miss = Placer::overfit(chull, m_bin); + if(miss < 0) miss = 0; + score += miss*miss; + } + + return score; + }; +} + +// Specialization for a generalized polygon. +// Warning: this is unfinished business. It may or may not work. +template<> +std::function AutoArranger::get_objfn() +{ + auto bincenter = sl::boundingBox(m_bin).center(); + return [this, bincenter](const Item &item) { + return std::get<0>(objfunc(item, bincenter)); + }; +} + +inline Circle to_lnCircle(const CircleBed& circ) { + return Circle({circ.center()(0), circ.center()(1)}, circ.radius()); +} + +// Get the type of bed geometry from a simple vector of points. +BedShapeHint::BedShapeHint(const Polyline &bed) { + auto x = [](const Point& p) { return p(X); }; + auto y = [](const Point& p) { return p(Y); }; + + auto width = [x](const BoundingBox& box) { + return x(box.max) - x(box.min); + }; + + auto height = [y](const BoundingBox& box) { + return y(box.max) - y(box.min); + }; + + auto area = [&width, &height](const BoundingBox& box) { + double w = width(box); + double h = height(box); + return w * h; + }; + + auto poly_area = [](Polyline p) { + Polygon pp; pp.points.reserve(p.points.size() + 1); + pp.points = std::move(p.points); + pp.points.emplace_back(pp.points.front()); + return std::abs(pp.area()); + }; + + auto distance_to = [x, y](const Point& p1, const Point& p2) { + double dx = x(p2) - x(p1); + double dy = y(p2) - y(p1); + return std::sqrt(dx*dx + dy*dy); + }; + + auto bb = bed.bounding_box(); + + auto isCircle = [bb, distance_to](const Polyline& polygon) { + auto center = bb.center(); + std::vector vertex_distances; + double avg_dist = 0; + for (auto pt: polygon.points) + { + double distance = distance_to(center, pt); + vertex_distances.push_back(distance); + avg_dist += distance; + } + + avg_dist /= vertex_distances.size(); + + CircleBed ret(center, avg_dist); + for(auto el : vertex_distances) + { + if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { + ret = CircleBed(); + break; + } + } + + return ret; + }; + + auto parea = poly_area(bed); + + if( (1.0 - parea/area(bb)) < 1e-3 ) { + m_type = BedShapes::bsBox; + m_bed.box = bb; + } + else if(auto c = isCircle(bed)) { + m_type = BedShapes::bsCircle; + m_bed.circ = c; + } else { + m_type = BedShapes::bsIrregular; + m_bed.polygon = bed; + } +} + +template // Arrange for arbitrary bin type +void _arrange( + std::vector & shapes, + std::vector & excludes, + const BinT & bin, + coord_t minobjd, + std::function prind, + std::function stopfn) +{ + // Integer ceiling the min distance from the bed perimeters + coord_t md = minobjd - 2 * scaled(0.1 + EPSILON); + md = (md % 2) ? md / 2 + 1 : md / 2; + + auto corrected_bin = bin; + sl::offset(corrected_bin, md); + + AutoArranger arranger{corrected_bin, 0, prind, stopfn}; + + auto infl = coord_t(std::ceil(minobjd / 2.0)); + for (Item& itm : shapes) itm.inflate(infl); + for (Item& itm : excludes) itm.inflate(infl); + + auto it = excludes.begin(); + while (it != excludes.end()) + sl::isInside(it->transformedShape(), corrected_bin) ? + ++it : it = excludes.erase(it); + + // If there is something on the plate + if (!excludes.empty()) arranger.preload(excludes); + + std::vector> inp; + inp.reserve(shapes.size() + excludes.size()); + for (auto &itm : shapes ) inp.emplace_back(itm); + for (auto &itm : excludes) inp.emplace_back(itm); + + arranger(inp.begin(), inp.end()); + for (Item &itm : inp) itm.inflate(-infl); +} + +// The final client function for arrangement. A progress indicator and +// a stop predicate can be also be passed to control the process. +void arrange(ArrangePolygons & arrangables, + const ArrangePolygons & excludes, + coord_t min_obj_dist, + const BedShapeHint & bedhint, + std::function progressind, + std::function stopcondition) +{ + namespace clppr = ClipperLib; + + std::vector items, fixeditems; + items.reserve(arrangables.size()); + + // Create Item from Arrangeable + auto process_arrangeable = + [](const ArrangePolygon &arrpoly, std::vector &outp) + { + Polygon p = arrpoly.poly.contour; + const Vec2crd & offs = arrpoly.translation; + double rotation = arrpoly.rotation; + + if (p.is_counter_clockwise()) p.reverse(); + + clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); + + auto firstp = clpath.Contour.front(); + clpath.Contour.emplace_back(firstp); + + outp.emplace_back(std::move(clpath)); + outp.back().rotation(rotation); + outp.back().translation({offs.x(), offs.y()}); + outp.back().binId(arrpoly.bed_idx); + outp.back().priority(arrpoly.priority); + }; + + for (ArrangePolygon &arrangeable : arrangables) + process_arrangeable(arrangeable, items); + + for (const ArrangePolygon &fixed: excludes) + process_arrangeable(fixed, fixeditems); + + for (Item &itm : fixeditems) itm.inflate(scaled(-2. * EPSILON)); + + auto &cfn = stopcondition; + auto &pri = progressind; + + switch (bedhint.get_type()) { + case bsBox: { + // Create the arranger for the box shaped bed + BoundingBox bbb = bedhint.get_box(); + Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; + + _arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn); + break; + } + case bsCircle: { + auto cc = to_lnCircle(bedhint.get_circle()); + + _arrange(items, fixeditems, cc, min_obj_dist, pri, cfn); + break; + } + case bsIrregular: { + auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.get_irregular()); + auto irrbed = sl::create(std::move(ctour)); + BoundingBox polybb(bedhint.get_irregular()); + + _arrange(items, fixeditems, irrbed, min_obj_dist, pri, cfn); + break; + } + case bsInfinite: { + const InfiniteBed& nobin = bedhint.get_infinite(); + auto infbb = Box::infinite({nobin.center.x(), nobin.center.y()}); + + _arrange(items, fixeditems, infbb, min_obj_dist, pri, cfn); + break; + } + case bsUnknown: { + // We know nothing about the bed, let it be infinite and zero centered + _arrange(items, fixeditems, Box::infinite(), min_obj_dist, pri, cfn); + break; + } + }; + + for(size_t i = 0; i < items.size(); ++i) { + clppr::IntPoint tr = items[i].translation(); + arrangables[i].translation = {coord_t(tr.X), coord_t(tr.Y)}; + arrangables[i].rotation = items[i].rotation(); + arrangables[i].bed_idx = items[i].binId(); + } +} + +// Arrange, without the fixed items (excludes) +void arrange(ArrangePolygons & inp, + coord_t min_d, + const BedShapeHint & bedhint, + std::function prfn, + std::function stopfn) +{ + arrange(inp, {}, min_d, bedhint, prfn, stopfn); +} + +} // namespace arr +} // namespace Slic3r diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp new file mode 100644 index 000000000..c02393dd9 --- /dev/null +++ b/src/libslic3r/Arrange.hpp @@ -0,0 +1,201 @@ +#ifndef MODELARRANGE_HPP +#define MODELARRANGE_HPP + +#include "ExPolygon.hpp" +#include "BoundingBox.hpp" + +namespace Slic3r { + +namespace arrangement { + +/// A geometry abstraction for a circular print bed. Similarly to BoundingBox. +class CircleBed { + Point center_; + double radius_; +public: + + inline CircleBed(): center_(0, 0), radius_(std::nan("")) {} + inline CircleBed(const Point& c, double r): center_(c), radius_(r) {} + + inline double radius() const { return radius_; } + inline const Point& center() const { return center_; } + inline operator bool() { return !std::isnan(radius_); } +}; + +/// Representing an unbounded bed. +struct InfiniteBed { Point center; }; + +/// Types of print bed shapes. +enum BedShapes { + bsBox, + bsCircle, + bsIrregular, + bsInfinite, + bsUnknown +}; + +/// Info about the print bed for the arrange() function. This is a variant +/// holding one of the four shapes a bed can be. +class BedShapeHint { + BedShapes m_type = BedShapes::bsInfinite; + + union BedShape_u { // TODO: use variant from cpp17? + CircleBed circ; + BoundingBox box; + Polyline polygon; + InfiniteBed infbed{}; + ~BedShape_u() {} + BedShape_u() {}; + } m_bed; + +public: + + BedShapeHint(){}; + + /// Get a bed shape hint for arrange() from a naked Polyline. + explicit BedShapeHint(const Polyline &polyl); + explicit BedShapeHint(const BoundingBox &bb) + { + m_type = bsBox; m_bed.box = bb; + } + + explicit BedShapeHint(const CircleBed &c) + { + m_type = bsCircle; m_bed.circ = c; + } + + explicit BedShapeHint(const InfiniteBed &ibed) + { + m_type = bsInfinite; m_bed.infbed = ibed; + } + + ~BedShapeHint() + { + if (m_type == BedShapes::bsIrregular) + m_bed.polygon.Slic3r::Polyline::~Polyline(); + }; + + BedShapeHint(const BedShapeHint &cpy) { *this = cpy; } + BedShapeHint(BedShapeHint &&cpy) { *this = std::move(cpy); } + + BedShapeHint &operator=(const BedShapeHint &cpy) + { + m_type = cpy.m_type; + switch(m_type) { + case bsBox: m_bed.box = cpy.m_bed.box; break; + case bsCircle: m_bed.circ = cpy.m_bed.circ; break; + case bsIrregular: m_bed.polygon = cpy.m_bed.polygon; break; + case bsInfinite: m_bed.infbed = cpy.m_bed.infbed; break; + case bsUnknown: break; + } + + return *this; + } + + BedShapeHint& operator=(BedShapeHint &&cpy) + { + m_type = cpy.m_type; + switch(m_type) { + case bsBox: m_bed.box = std::move(cpy.m_bed.box); break; + case bsCircle: m_bed.circ = std::move(cpy.m_bed.circ); break; + case bsIrregular: m_bed.polygon = std::move(cpy.m_bed.polygon); break; + case bsInfinite: m_bed.infbed = std::move(cpy.m_bed.infbed); break; + case bsUnknown: break; + } + + return *this; + } + + BedShapes get_type() const { return m_type; } + + const BoundingBox &get_box() const + { + assert(m_type == bsBox); return m_bed.box; + } + const CircleBed &get_circle() const + { + assert(m_type == bsCircle); return m_bed.circ; + } + const Polyline &get_irregular() const + { + assert(m_type == bsIrregular); return m_bed.polygon; + } + const InfiniteBed &get_infinite() const + { + assert(m_type == bsInfinite); return m_bed.infbed; + } +}; + +/// A logical bed representing an object not being arranged. Either the arrange +/// has not yet successfully run on this ArrangePolygon or it could not fit the +/// object due to overly large size or invalid geometry. +static const constexpr int UNARRANGED = -1; + +/// Input/Output structure for the arrange() function. The poly field will not +/// be modified during arrangement. Instead, the translation and rotation fields +/// will mark the needed transformation for the polygon to be in the arranged +/// position. These can also be set to an initial offset and rotation. +/// +/// The bed_idx field will indicate the logical bed into which the +/// polygon belongs: UNARRANGED means no place for the polygon +/// (also the initial state before arrange), 0..N means the index of the bed. +/// Zero is the physical bed, larger than zero means a virtual bed. +struct ArrangePolygon { + ExPolygon poly; /// The 2D silhouette to be arranged + Vec2crd translation{0, 0}; /// The translation of the poly + double rotation{0.0}; /// The rotation of the poly in radians + int bed_idx{UNARRANGED}; /// To which logical bed does poly belong... + int priority{0}; + + /// Optional setter function which can store arbitrary data in its closure + std::function setter = nullptr; + + /// Helper function to call the setter with the arrange data arguments + void apply() const { if (setter) setter(*this); } + + /// Test if arrange() was called previously and gave a successful result. + bool is_arranged() const { return bed_idx != UNARRANGED; } +}; + +using ArrangePolygons = std::vector; + +/** + * \brief Arranges the input polygons. + * + * WARNING: Currently, only convex polygons are supported by the libnest2d + * library which is used to do the arrangement. This might change in the future + * this is why the interface contains a general polygon capable to have holes. + * + * \param items Input vector of ArrangePolygons. The transformation, rotation + * and bin_idx fields will be changed after the call finished and can be used + * to apply the result on the input polygon. + * + * \param min_obj_distance The minimum distance which is allowed for any + * pair of items on the print bed in any direction. + * + * \param bedhint Info about the shape and type of the bed. + * + * \param progressind Progress indicator callback called when + * an object gets packed. The unsigned argument is the number of items + * remaining to pack. + * + * \param stopcondition A predicate returning true if abort is needed. + */ +void arrange(ArrangePolygons & items, + coord_t min_obj_distance, + const BedShapeHint & bedhint, + std::function progressind = nullptr, + std::function stopcondition = nullptr); + +/// Same as the previous, only that it takes unmovable items as an +/// additional argument. Those will be considered as already arranged objects. +void arrange(ArrangePolygons & items, + const ArrangePolygons & excludes, + coord_t min_obj_distance, + const BedShapeHint & bedhint, + std::function progressind = nullptr, + std::function stopcondition = nullptr); + +} // arr +} // Slic3r +#endif // MODELARRANGE_HPP diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index deb0ca43b..ac40e99bc 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -106,8 +106,8 @@ add_library(libslic3r STATIC Line.hpp Model.cpp Model.hpp - ModelArrange.hpp - ModelArrange.cpp + Arrange.hpp + Arrange.cpp MotionPlanner.cpp MotionPlanner.hpp MultiPoint.cpp diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 42992223f..1fef4b475 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -314,49 +314,48 @@ template struct is_scaled_coord }; // Meta predicates for floating, 'scaled coord' and generic arithmetic types -template -using FloatingOnly = enable_if_t::value, T>; +template +using FloatingOnly = enable_if_t::value, O>; -template -using ScaledCoordOnly = enable_if_t::value, T>; +template +using ScaledCoordOnly = enable_if_t::value, O>; -template -using ArithmeticOnly = enable_if_t::value, T>; - -// A shorter form for a generic Eigen vector which is widely used in PrusaSlicer -template -using EigenVec = Eigen::Matrix; +template +using ArithmeticOnly = enable_if_t::value, O>; // Semantics are the following: // Upscaling (scaled()): only from floating point types (or Vec) to either // floating point or integer 'scaled coord' coordinates. -// Downscaling (unscaled()): from arithmetic types (or Vec) to either -// floating point only +// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only // Conversion definition from unscaled to floating point scaled template, - class = FloatingOnly> -inline SLIC3R_CONSTEXPR Tout scaled(const Tin &v) SLIC3R_NOEXCEPT + class = FloatingOnly> +inline constexpr FloatingOnly scaled(const Tin &v) noexcept { - return static_cast(v / static_cast(SCALING_FACTOR)); + return Tout(v / Tin(SCALING_FACTOR)); } // Conversion definition from unscaled to integer 'scaled coord'. -// TODO: is the rounding necessary ? Here it is to show that it can be different -// but it does not have to be. Using std::round means loosing noexcept and -// constexpr modifiers +// TODO: is the rounding necessary? Here it is commented out to show that +// it can be different for integers but it does not have to be. Using +// std::round means loosing noexcept and constexpr modifiers template> -inline SLIC3R_CONSTEXPR ScaledCoordOnly scaled(const Tin &v) SLIC3R_NOEXCEPT +inline constexpr ScaledCoordOnly scaled(const Tin &v) noexcept { //return static_cast(std::round(v / SCALING_FACTOR)); - return static_cast(v / static_cast(SCALING_FACTOR)); + return Tout(v / Tin(SCALING_FACTOR)); } // Conversion for Eigen vectors (N dimensional points) -template> -inline EigenVec, N> scaled(const EigenVec &v) +template, + int...EigenArgs> +inline Eigen::Matrix, N, EigenArgs...> +scaled(const Eigen::Matrix &v) { return (v / SCALING_FACTOR).template cast(); } @@ -366,9 +365,9 @@ template, class = FloatingOnly> -inline SLIC3R_CONSTEXPR Tout unscaled(const Tin &v) SLIC3R_NOEXCEPT +inline constexpr Tout unscaled(const Tin &v) noexcept { - return static_cast(v * static_cast(SCALING_FACTOR)); + return Tout(v * Tout(SCALING_FACTOR)); } // Unscaling for Eigen vectors. Input base type can be arithmetic, output base @@ -377,9 +376,10 @@ template, - class = FloatingOnly> -inline SLIC3R_CONSTEXPR EigenVec unscaled( - const EigenVec &v) SLIC3R_NOEXCEPT + class = FloatingOnly, + int...EigenArgs> +inline constexpr Eigen::Matrix +unscaled(const Eigen::Matrix &v) noexcept { return v.template cast() * SCALING_FACTOR; } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 21e155793..ecfb6697a 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1,5 +1,6 @@ #include "Model.hpp" #include "Geometry.hpp" +#include "MTUtils.hpp" #include "Format/AMF.hpp" #include "Format/OBJ.hpp" @@ -369,34 +370,44 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb /* arrange objects preserving their instance count but altering their instance positions */ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) -{ - // get the (transformed) size of each instance so that we take - // into account their different transformations when packing - Pointfs instance_sizes; - Pointfs instance_centers; - for (const ModelObject *o : this->objects) - for (size_t i = 0; i < o->instances.size(); ++ i) { - // an accurate snug bounding box around the transformed mesh. - BoundingBoxf3 bbox(o->instance_bounding_box(i, true)); - instance_sizes.emplace_back(to_2d(bbox.size())); - instance_centers.emplace_back(to_2d(bbox.center())); +{ + size_t count = 0; + for (auto obj : objects) count += obj->instances.size(); + + arrangement::ArrangePolygons input; + ModelInstancePtrs instances; + input.reserve(count); + instances.reserve(count); + for (ModelObject *mo : objects) + for (ModelInstance *minst : mo->instances) { + input.emplace_back(minst->get_arrange_polygon()); + instances.emplace_back(minst); } - - Pointfs positions; - if (! _arrange(instance_sizes, dist, bb, positions)) - return false; - - size_t idx = 0; - for (ModelObject *o : this->objects) { - for (ModelInstance *i : o->instances) { - Vec2d offset_xy = positions[idx] - instance_centers[idx]; - i->set_offset(Vec3d(offset_xy(0), offset_xy(1), i->get_offset(Z))); - ++idx; - } - o->invalidate_bounding_box(); + + arrangement::BedShapeHint bedhint; + coord_t bedwidth = 0; + + if (bb) { + bedwidth = scaled(bb->size().x()); + bedhint = arrangement::BedShapeHint( + BoundingBox(scaled(bb->min), scaled(bb->max))); } - return true; + arrangement::arrange(input, scaled(dist), bedhint); + + bool ret = true; + coord_t stride = bedwidth + bedwidth / 5; + + for(size_t i = 0; i < input.size(); ++i) { + if (input[i].bed_idx != 0) ret = false; + if (input[i].bed_idx >= 0) { + input[i].translation += Vec2crd{input[i].bed_idx * stride, 0}; + instances[i]->apply_arrange_result(input[i].translation, + input[i].rotation); + } + } + + return ret; } // Duplicate the entire model preserving instance relative positions. @@ -1814,6 +1825,36 @@ void ModelInstance::transform_polygon(Polygon* polygon) const polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin } +arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const +{ + static const double SIMPLIFY_TOLERANCE_MM = 0.1; + + Vec3d rotation = get_rotation(); + rotation.z() = 0.; + Transform3d trafo_instance = + Geometry::assemble_transform(Vec3d::Zero(), rotation, + get_scaling_factor(), get_mirror()); + + Polygon p = get_object()->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()) { + Polygons pp{p}; + pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); + if (!pp.empty()) p = pp.front(); + } + + arrangement::ArrangePolygon ret; + ret.poly.contour = std::move(p); + ret.translation = Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))}; + ret.rotation = get_rotation(Z); + + return ret; +} + // Test whether the two models contain the same number of ModelObjects with the same set of IDs // ordered in the same order. In that case it is not necessary to kill the background processing. bool model_object_list_equal(const Model &model_old, const Model &model_new) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index f03d53153..4bd2c7473 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -10,6 +10,7 @@ #include "Slicing.hpp" #include "SLA/SLACommon.hpp" #include "TriangleMesh.hpp" +#include "Arrange.hpp" #include #include @@ -602,7 +603,7 @@ public: const Vec3d& get_offset() const { return m_transformation.get_offset(); } double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } - + void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } @@ -621,7 +622,7 @@ public: const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } bool is_left_handed() const { return m_transformation.is_left_handed(); } - + void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } @@ -639,6 +640,18 @@ public: const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } bool is_printable() const { return print_volume_state == PVS_Inside; } + + // Getting the input polygon for arrange + arrangement::ArrangePolygon get_arrange_polygon() const; + + // Apply the arrange result on the ModelInstance + void apply_arrange_result(const Vec2crd& offs, double rotation) + { + // write the transformation data into the model instance + set_rotation(Z, rotation); + set_offset(X, unscale(offs(X))); + set_offset(Y, unscale(offs(Y))); + } protected: friend class Print; @@ -654,10 +667,10 @@ private: ModelObject* object; // Constructor, which assigns a new unique ID. - explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) { assert(this->id().valid()); } + explicit ModelInstance(ModelObject *object) : print_volume_state(PVS_Inside), object(object) { assert(this->id().valid()); } // Constructor, which assigns a new unique ID. explicit ModelInstance(ModelObject *object, const ModelInstance &other) : - m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) { assert(this->id().valid() && this->id() != other.id()); } + m_transformation(other.m_transformation), print_volume_state(PVS_Inside), object(object) { assert(this->id().valid() && this->id() != other.id()); } explicit ModelInstance(ModelInstance &&rhs) = delete; ModelInstance& operator=(const ModelInstance &rhs) = delete; diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp deleted file mode 100644 index d3586651b..000000000 --- a/src/libslic3r/ModelArrange.cpp +++ /dev/null @@ -1,1069 +0,0 @@ -#include "ModelArrange.hpp" -#include "Model.hpp" -#include "Geometry.hpp" -#include "SVG.hpp" -#include "MTUtils.hpp" - -#include - -#include -#include - -#include -#include -#include - -namespace libnest2d { -#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__) -using LargeInt = __int128; -#else -using LargeInt = boost::multiprecision::int128_t; -template<> struct _NumTag { using Type = ScalarTag; }; -#endif -template struct _NumTag> { using Type = RationalTag; }; - -namespace nfp { - -template -struct NfpImpl -{ - NfpResult operator()(const S &sh, const S &other) - { - return nfpConvexOnly>(sh, other); - } -}; - -} -} - -namespace Slic3r { - -namespace arr { - -using namespace libnest2d; - -// Only for debugging. Prints the model object vertices on stdout. -std::string toString(const Model& model, bool holes = true) { - std::stringstream ss; - - ss << "{\n"; - - for(auto objptr : model.objects) { - if(!objptr) continue; - - auto rmesh = objptr->raw_mesh(); - - for(auto objinst : objptr->instances) { - if(!objinst) continue; - - Slic3r::TriangleMesh tmpmesh = rmesh; - // CHECK_ME -> Is the following correct ? - tmpmesh.scale(objinst->get_scaling_factor()); - objinst->transform_mesh(&tmpmesh); - ExPolygons expolys = tmpmesh.horizontal_projection(); - for(auto& expoly_complex : expolys) { - - ExPolygons tmp = expoly_complex.simplify(scaled(1.)); - if(tmp.empty()) continue; - ExPolygon expoly = tmp.front(); - expoly.contour.make_clockwise(); - for(auto& h : expoly.holes) h.make_counter_clockwise(); - - ss << "\t{\n"; - ss << "\t\t{\n"; - - for(auto v : expoly.contour.points) ss << "\t\t\t{" - << v(0) << ", " - << v(1) << "},\n"; - { - auto v = expoly.contour.points.front(); - ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n"; - } - ss << "\t\t},\n"; - - // Holes: - ss << "\t\t{\n"; - if(holes) for(auto h : expoly.holes) { - ss << "\t\t\t{\n"; - for(auto v : h.points) ss << "\t\t\t\t{" - << v(0) << ", " - << v(1) << "},\n"; - { - auto v = h.points.front(); - ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n"; - } - ss << "\t\t\t},\n"; - } - ss << "\t\t},\n"; - - ss << "\t},\n"; - } - } - } - - ss << "}\n"; - - return ss.str(); -} - -// Debugging: Save model to svg file. -void toSVG(SVG& svg, const Model& model) { - for(auto objptr : model.objects) { - if(!objptr) continue; - - auto rmesh = objptr->raw_mesh(); - - for(auto objinst : objptr->instances) { - if(!objinst) continue; - - Slic3r::TriangleMesh tmpmesh = rmesh; - tmpmesh.scale(objinst->get_scaling_factor()); - objinst->transform_mesh(&tmpmesh); - ExPolygons expolys = tmpmesh.horizontal_projection(); - svg.draw(expolys); - } - } -} - -namespace bgi = boost::geometry::index; - -using SpatElement = std::pair; -using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; -using ItemGroup = std::vector>; -template -using TPacker = typename placers::_NofitPolyPlacer; - -const double BIG_ITEM_TRESHOLD = 0.02; - -Box boundingBox(const Box& pilebb, const Box& ibb ) { - auto& pminc = pilebb.minCorner(); - auto& pmaxc = pilebb.maxCorner(); - auto& iminc = ibb.minCorner(); - auto& imaxc = ibb.maxCorner(); - PointImpl minc, maxc; - - setX(minc, std::min(getX(pminc), getX(iminc))); - setY(minc, std::min(getY(pminc), getY(iminc))); - - setX(maxc, std::max(getX(pmaxc), getX(imaxc))); - setY(maxc, std::max(getY(pmaxc), getY(imaxc))); - return Box(minc, maxc); -} - -// This is "the" object function which is evaluated many times for each vertex -// (decimated with the accuracy parameter) of each object. Therefore it is -// upmost crucial for this function to be as efficient as it possibly can be but -// at the same time, it has to provide reasonable results. -std::tuple -objfunc(const PointImpl& bincenter, - const TMultiShape& merged_pile, - const Box& pilebb, - const ItemGroup& items, - const Item &item, - double bin_area, - double norm, // A norming factor for physical dimensions - // a spatial index to quickly get neighbors of the candidate item - const SpatIndex& spatindex, - const SpatIndex& smalls_spatindex, - const ItemGroup& remaining - ) -{ - // We will treat big items (compared to the print bed) differently - auto isBig = [bin_area](double a) { - return a/bin_area > BIG_ITEM_TRESHOLD ; - }; - - // Candidate item bounding box - auto ibb = sl::boundingBox(item.transformedShape()); - - // Calculate the full bounding box of the pile with the candidate item - auto fullbb = boundingBox(pilebb, ibb); - - // The bounding box of the big items (they will accumulate in the center - // of the pile - Box bigbb; - if(spatindex.empty()) bigbb = fullbb; - else { - auto boostbb = spatindex.bounds(); - boost::geometry::convert(boostbb, bigbb); - } - - // Will hold the resulting score - double score = 0; - - if(isBig(item.area()) || spatindex.empty()) { - // This branch is for the bigger items.. - - auto minc = ibb.minCorner(); // bottom left corner - auto maxc = ibb.maxCorner(); // top right corner - - // top left and bottom right corners - auto top_left = PointImpl{getX(minc), getY(maxc)}; - auto bottom_right = PointImpl{getX(maxc), getY(minc)}; - - // Now the distance of the gravity center will be calculated to the - // five anchor points and the smallest will be chosen. - std::array dists; - auto cc = fullbb.center(); // The gravity center - dists[0] = pl::distance(minc, cc); - dists[1] = pl::distance(maxc, cc); - dists[2] = pl::distance(ibb.center(), cc); - dists[3] = pl::distance(top_left, cc); - dists[4] = pl::distance(bottom_right, cc); - - // The smalles distance from the arranged pile center: - auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; - auto bindist = pl::distance(ibb.center(), bincenter) / norm; - dist = 0.8*dist + 0.2*bindist; - - // Density is the pack density: how big is the arranged pile - double density = 0; - - if(remaining.empty()) { - - auto mp = merged_pile; - mp.emplace_back(item.transformedShape()); - auto chull = sl::convexHull(mp); - - placers::EdgeCache ec(chull); - - double circ = ec.circumference() / norm; - double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; - score = 0.5*circ + 0.5*bcirc; - - } else { - // Prepare a variable for the alignment score. - // This will indicate: how well is the candidate item aligned with - // its neighbors. We will check the alignment with all neighbors and - // return the score for the best alignment. So it is enough for the - // candidate to be aligned with only one item. - auto alignment_score = 1.0; - - density = std::sqrt((fullbb.width() / norm )* - (fullbb.height() / norm)); - auto querybb = item.boundingBox(); - - // Query the spatial index for the neighbors - std::vector result; - result.reserve(spatindex.size()); - if(isBig(item.area())) { - spatindex.query(bgi::intersects(querybb), - std::back_inserter(result)); - } else { - smalls_spatindex.query(bgi::intersects(querybb), - std::back_inserter(result)); - } - - for(auto& e : result) { // now get the score for the best alignment - auto idx = e.second; - Item& p = items[idx]; - auto parea = p.area(); - if(std::abs(1.0 - parea/item.area()) < 1e-6) { - auto bb = boundingBox(p.boundingBox(), ibb); - auto bbarea = bb.area(); - auto ascore = 1.0 - (item.area() + parea)/bbarea; - - if(ascore < alignment_score) alignment_score = ascore; - } - } - - // The final mix of the score is the balance between the distance - // from the full pile center, the pack density and the - // alignment with the neighbors - if(result.empty()) - score = 0.5 * dist + 0.5 * density; - else - score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; - } - } else { - // Here there are the small items that should be placed around the - // already processed bigger items. - // No need to play around with the anchor points, the center will be - // just fine for small items - score = pl::distance(ibb.center(), bigbb.center()) / norm; - } - - return std::make_tuple(score, fullbb); -} - -// Fill in the placer algorithm configuration with values carefully chosen for -// Slic3r. -template -void fillConfig(PConf& pcfg) { - - // Align the arranged pile into the center of the bin - pcfg.alignment = PConf::Alignment::CENTER; - - // Start placing the items from the center of the print bed - pcfg.starting_point = PConf::Alignment::CENTER; - - // TODO cannot use rotations until multiple objects of same geometry can - // handle different rotations - // arranger.useMinimumBoundigBoxRotation(); - pcfg.rotations = { 0.0 }; - - // The accuracy of optimization. - // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 0.65f; - - pcfg.parallel = true; -} - -// Type trait for an arranger class for different bin types (box, circle, -// polygon, etc...) -template -class AutoArranger {}; - - -// A class encapsulating the libnest2d Nester class and extending it with other -// management and spatial index structures for acceleration. -template -class _ArrBase { -public: - - // Useful type shortcuts... - using Placer = TPacker; - using Selector = FirstFitSelection; - using Packer = Nester; - using PConfig = typename Packer::PlacementConfig; - using Distance = TCoord; - using Pile = TMultiShape; - -protected: - - Packer m_pck; - PConfig m_pconf; // Placement configuration - double m_bin_area; - SpatIndex m_rtree; // spatial index for the normal (bigger) objects - SpatIndex m_smallsrtree; // spatial index for only the smaller items - double m_norm; // A coefficient to scale distances - Pile m_merged_pile; // The already merged pile (vector of items) - Box m_pilebb; // The bounding box of the merged pile. - ItemGroup m_remaining; // Remaining items (m_items at the beginning) - ItemGroup m_items; // The items to be packed -public: - - _ArrBase(const TBin& bin, Distance dist, - std::function progressind, - std::function stopcond): - m_pck(bin, dist), m_bin_area(sl::area(bin)), - m_norm(std::sqrt(sl::area(bin))) - { - fillConfig(m_pconf); - - // Set up a callback that is called just before arranging starts - // This functionality is provided by the Nester class (m_pack). - m_pconf.before_packing = - [this](const Pile& merged_pile, // merged pile - const ItemGroup& items, // packed items - const ItemGroup& remaining) // future items to be packed - { - m_items = items; - m_merged_pile = merged_pile; - m_remaining = remaining; - - m_pilebb = sl::boundingBox(merged_pile); - - m_rtree.clear(); - m_smallsrtree.clear(); - - // We will treat big items (compared to the print bed) differently - auto isBig = [this](double a) { - return a/m_bin_area > BIG_ITEM_TRESHOLD ; - }; - - for(unsigned idx = 0; idx < items.size(); ++idx) { - Item& itm = items[idx]; - if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx}); - m_smallsrtree.insert({itm.boundingBox(), idx}); - } - }; - - m_pck.progressIndicator(progressind); - m_pck.stopCondition(stopcond); - } - - template inline IndexedPackGroup operator()(Args&&...args) { - m_rtree.clear(); - return m_pck.executeIndexed(std::forward(args)...); - } - - inline void preload(const PackGroup& pg) { - m_pconf.alignment = PConfig::Alignment::DONT_ALIGN; - m_pconf.object_function = nullptr; // drop the special objectfunction - m_pck.preload(pg); - - // Build the rtree for queries to work - for(const ItemGroup& grp : pg) - for(unsigned idx = 0; idx < grp.size(); ++idx) { - Item& itm = grp[idx]; - m_rtree.insert({itm.boundingBox(), idx}); - } - - m_pck.configure(m_pconf); - } - - bool is_colliding(const Item& item) { - if(m_rtree.empty()) return false; - std::vector result; - m_rtree.query(bgi::intersects(item.boundingBox()), - std::back_inserter(result)); - return !result.empty(); - } -}; - -// Arranger specialization for a Box shaped bin. -template<> class AutoArranger: public _ArrBase { -public: - - AutoArranger(const Box& bin, Distance dist, - std::function progressind = [](unsigned){}, - std::function stopcond = [](){return false;}): - _ArrBase(bin, dist, progressind, stopcond) - { - - // Here we set up the actual object function that calls the common - // object function for all bin shapes than does an additional inside - // check for the arranged pile. - m_pconf.object_function = [this, bin] (const Item &item) { - - auto result = objfunc(bin.center(), - m_merged_pile, - m_pilebb, - m_items, - item, - m_bin_area, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); - - double score = std::get<0>(result); - auto& fullbb = std::get<1>(result); - - double miss = Placer::overfit(fullbb, bin); - miss = miss > 0? miss : 0; - score += miss*miss; - - return score; - }; - - m_pck.configure(m_pconf); - } -}; - -using lnCircle = libnest2d::_Circle; - -inline lnCircle to_lnCircle(const Circle& circ) { - return lnCircle({circ.center()(0), circ.center()(1)}, circ.radius()); -} - -// Arranger specialization for circle shaped bin. -template<> class AutoArranger: public _ArrBase { -public: - - AutoArranger(const lnCircle& bin, Distance dist, - std::function progressind = [](unsigned){}, - std::function stopcond = [](){return false;}): - _ArrBase(bin, dist, progressind, stopcond) { - - // As with the box, only the inside check is different. - m_pconf.object_function = [this, &bin] (const Item &item) { - - auto result = objfunc(bin.center(), - m_merged_pile, - m_pilebb, - m_items, - item, - m_bin_area, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); - - double score = std::get<0>(result); - - auto isBig = [this](const Item& itm) { - return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ; - }; - - if(isBig(item)) { - auto mp = m_merged_pile; - mp.push_back(item.transformedShape()); - auto chull = sl::convexHull(mp); - double miss = Placer::overfit(chull, bin); - if(miss < 0) miss = 0; - score += miss*miss; - } - - return score; - }; - - m_pck.configure(m_pconf); - } -}; - -// Arranger specialization for a generalized polygon. -// Warning: this is unfinished business. It may or may not work. -template<> class AutoArranger: public _ArrBase { -public: - AutoArranger(const PolygonImpl& bin, Distance dist, - std::function progressind = [](unsigned){}, - std::function stopcond = [](){return false;}): - _ArrBase(bin, dist, progressind, stopcond) - { - m_pconf.object_function = [this, &bin] (const Item &item) { - - auto binbb = sl::boundingBox(bin); - auto result = objfunc(binbb.center(), - m_merged_pile, - m_pilebb, - m_items, - item, - m_bin_area, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); - double score = std::get<0>(result); - - return score; - }; - - m_pck.configure(m_pconf); - } -}; - -// Specialization with no bin. In this case the arranger should just arrange -// all objects into a minimum sized pile but it is not limited by a bin. A -// consequence is that only one pile should be created. -template<> class AutoArranger: public _ArrBase { -public: - - AutoArranger(Distance dist, std::function progressind, - std::function stopcond): - _ArrBase(Box(0, 0), dist, progressind, stopcond) - { - this->m_pconf.object_function = [this] (const Item &item) { - - auto result = objfunc({0, 0}, - m_merged_pile, - m_pilebb, - m_items, - item, - 0, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); - return std::get<0>(result); - }; - - this->m_pck.configure(m_pconf); - } -}; - -// A container which stores a pointer to the 3D object and its projected -// 2D shape from top view. -using ShapeData2D = std::vector>; - -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) - auto s = std::accumulate(model.objects.begin(), model.objects.end(), - size_t(0), [](size_t s, ModelObject* o) - { - return s + o->instances.size(); - }); - - ret.reserve(s); - - for(ModelObject* objptr : model.objects) { - if (! objptr->instances.empty()) { - - // TODO export the exact 2D projection. Cannot do it as libnest2d - // does not support concave shapes (yet). - ClipperLib::Path clpath; - - // Object instances should carry the same scaling and - // x, y rotation that is why we use the first instance. - { - 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()); - 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; - - if(tolerance > EPSILON) { - Polygons pp { p }; - pp = p.simplify(scaled(tolerance)); - if (!pp.empty()) p = pp.front(); - } - - p.reverse(); - assert(!p.is_counter_clockwise()); - clpath = Slic3rMultiPoint_to_ClipperPath(p); - auto firstp = clpath.front(); clpath.emplace_back(firstp); - } - - Vec3d rotation0 = objptr->instances.front()->get_rotation(); - rotation0(2) = 0.; - for(ModelInstance* objinst : objptr->instances) { - ClipperLib::Polygon pn; - pn.Contour = clpath; - - // Efficient conversion to item. - Item item(std::move(pn)); - - // Invalid geometries would throw exceptions when arranging - if(item.vertexCount() > 3) { - item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation())); - item.translation({ - scaled(objinst->get_offset(X)), - scaled(objinst->get_offset(Y)) - }); - ret.emplace_back(objinst, item); - } - } - } - } - - // The wipe tower is a separate case (in case there is one), let's duplicate the code - if (wti.is_wipe_tower) { - Points pts; - pts.emplace_back(coord_t(scale_(0.)), coord_t(scale_(0.))); - pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(0.))); - pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(wti.bb_size(1)))); - pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(wti.bb_size(1)))); - pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(0.))); - Polygon p(std::move(pts)); - ClipperLib::Path clpath = Slic3rMultiPoint_to_ClipperPath(p); - ClipperLib::Polygon pn; - pn.Contour = clpath; - // Efficient conversion to item. - Item item(std::move(pn)); - item.rotation(wti.rotation), - item.translation({ - scaled(wti.pos(0)), - scaled(wti.pos(1)) - }); - ret.emplace_back(nullptr, item); - } - - return ret; -} - -// Apply the calculated translations and rotations (currently disabled) to the -// Model object instances. -void applyResult( - IndexedPackGroup::value_type& group, - Coord batch_offset, - ShapeData2D& shapemap, - WipeTowerInfo& wti) -{ - for(auto& r : group) { - auto idx = r.first; // get the original item index - Item& item = r.second; // get the item itself - - // Get the model instance from the shapemap using the index - ModelInstance *inst_ptr = shapemap[idx].first; - - // Get the transformation data from the item object and scale it - // appropriately - auto off = item.translation(); - Radians rot = item.rotation(); - - Vec3d foff(off.X*SCALING_FACTOR + batch_offset, - off.Y*SCALING_FACTOR, - inst_ptr ? inst_ptr->get_offset()(Z) : 0.); - - if (inst_ptr) { - // write the transformation data into the model instance - inst_ptr->set_rotation(Z, rot); - inst_ptr->set_offset(foff); - } - else { // this is the wipe tower - we will modify the struct with the info - // and leave it up to the called to actually move the wipe tower - wti.pos = Vec2d(foff(0), foff(1)); - wti.rotation = rot; - } - } -} - -// Get the type of bed geometry from a simple vector of points. -BedShapeHint bedShape(const Polyline &bed) { - BedShapeHint ret; - - auto x = [](const Point& p) { return p(0); }; - auto y = [](const Point& p) { return p(1); }; - - auto width = [x](const BoundingBox& box) { - return x(box.max) - x(box.min); - }; - - auto height = [y](const BoundingBox& box) { - return y(box.max) - y(box.min); - }; - - auto area = [&width, &height](const BoundingBox& box) { - double w = width(box); - double h = height(box); - return w*h; - }; - - auto poly_area = [](Polyline p) { - Polygon pp; pp.points.reserve(p.points.size() + 1); - pp.points = std::move(p.points); - pp.points.emplace_back(pp.points.front()); - return std::abs(pp.area()); - }; - - auto distance_to = [x, y](const Point& p1, const Point& p2) { - double dx = x(p2) - x(p1); - double dy = y(p2) - y(p1); - return std::sqrt(dx*dx + dy*dy); - }; - - auto bb = bed.bounding_box(); - - auto isCircle = [bb, distance_to](const Polyline& polygon) { - auto center = bb.center(); - std::vector vertex_distances; - double avg_dist = 0; - for (auto pt: polygon.points) - { - double distance = distance_to(center, pt); - vertex_distances.push_back(distance); - avg_dist += distance; - } - - avg_dist /= vertex_distances.size(); - - Circle ret(center, avg_dist); - for(auto el : vertex_distances) - { - if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { - ret = Circle(); - break; - } - } - - return ret; - }; - - auto parea = poly_area(bed); - - if( (1.0 - parea/area(bb)) < 1e-3 ) { - ret.type = BedShapeType::BOX; - ret.shape.box = bb; - } - else if(auto c = isCircle(bed)) { - ret.type = BedShapeType::CIRCLE; - ret.shape.circ = c; - } else { - ret.type = BedShapeType::IRREGULAR; - ret.shape.polygon = bed; - } - - // Determine the bed shape by hand - return ret; -} - -static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; - -// The final client function to arrange the Model. A progress indicator and -// a stop predicate can be also be passed to control the process. -bool arrange(Model &model, // The model with the geometries - WipeTowerInfo& wti, // Wipe tower info - coord_t min_obj_distance, // Has to be in scaled (clipper) measure - const Polyline &bed, // The bed geometry. - BedShapeHint bedhint, // Hint about the bed geometry type. - bool first_bin_only, // What to do is not all items fit. - - // Controlling callbacks. - std::function progressind, - std::function stopcondition) -{ - bool ret = true; - - // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); - - // Copy the references for the shapes only as the arranger expects a - // sequence of objects convertible to Item or ClipperPolygon - std::vector> shapes; - shapes.reserve(shapemap.size()); - std::for_each(shapemap.begin(), shapemap.end(), - [&shapes] (ShapeData2D::value_type& it) - { - shapes.push_back(std::ref(it.second)); - }); - - IndexedPackGroup result; - - // If there is no hint about the shape, we will try to guess - if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); - - BoundingBox bbb(bed); - - auto& cfn = stopcondition; - - // Integer ceiling the min distance from the bed perimeters - coord_t md = min_obj_distance - SCALED_EPSILON; - md = (md % 2) ? md / 2 + 1 : md / 2; - - auto binbb = Box({libnest2d::Coord{bbb.min(0)} - md, - libnest2d::Coord{bbb.min(1)} - md}, - {libnest2d::Coord{bbb.max(0)} + md, - libnest2d::Coord{bbb.max(1)} + md}); - - switch(bedhint.type) { - case BedShapeType::BOX: { - - // Create the arranger for the box shaped bed - AutoArranger arrange(binbb, min_obj_distance, progressind, cfn); - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); - break; - } - case BedShapeType::CIRCLE: { - - auto c = bedhint.shape.circ; - auto cc = to_lnCircle(c); - - AutoArranger arrange(cc, min_obj_distance, progressind, cfn); - result = arrange(shapes.begin(), shapes.end()); - break; - } - case BedShapeType::IRREGULAR: - case BedShapeType::WHO_KNOWS: { - - using P = libnest2d::PolygonImpl; - - auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); - P irrbed = sl::create(std::move(ctour)); - - AutoArranger

arrange(irrbed, min_obj_distance, progressind, cfn); - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); - break; - } - }; - - if(result.empty() || stopcondition()) return false; - - if(first_bin_only) { - applyResult(result.front(), 0, shapemap, wti); - } else { - - const auto STRIDE_PADDING = 1.2; - - Coord stride = static_cast(STRIDE_PADDING* - binbb.width()*SCALING_FACTOR); - Coord batch_offset = 0; - - for(auto& group : result) { - applyResult(group, batch_offset, shapemap, wti); - - // Only the first pack group can be placed onto the print bed. The - // other objects which could not fit will be placed next to the - // print bed - batch_offset += stride; - } - } - - for(auto objptr : model.objects) objptr->invalidate_bounding_box(); - - return ret && result.size() == 1; -} - -void find_new_position(const Model &model, - ModelInstancePtrs toadd, - coord_t min_obj_distance, - const Polyline &bed, - WipeTowerInfo& wti) -{ - // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); - - // Copy the references for the shapes only as the arranger expects a - // sequence of objects convertible to Item or ClipperPolygon - PackGroup preshapes; preshapes.emplace_back(); - ItemGroup shapes; - preshapes.front().reserve(shapemap.size()); - - std::vector shapes_ptr; shapes_ptr.reserve(toadd.size()); - IndexedPackGroup result; - - // If there is no hint about the shape, we will try to guess - BedShapeHint bedhint = bedShape(bed); - - BoundingBox bbb(bed); - - // Integer ceiling the min distance from the bed perimeters - coord_t md = min_obj_distance - SCALED_EPSILON; - md = (md % 2) ? md / 2 + 1 : md / 2; - - auto binbb = Box({libnest2d::Coord{bbb.min(0)} - md, - libnest2d::Coord{bbb.min(1)} - md}, - {libnest2d::Coord{bbb.max(0)} + md, - libnest2d::Coord{bbb.max(1)} + md}); - - for(auto it = shapemap.begin(); it != shapemap.end(); ++it) { - if(std::find(toadd.begin(), toadd.end(), it->first) == toadd.end()) { - if(it->second.isInside(binbb)) // just ignore items which are outside - preshapes.front().emplace_back(std::ref(it->second)); - } - else { - shapes_ptr.emplace_back(it->first); - shapes.emplace_back(std::ref(it->second)); - } - } - - auto try_first_to_center = [&shapes, &shapes_ptr, &binbb] - (std::function is_colliding, - std::function preload) - { - // Try to put the first item to the center, as the arranger will not - // do this for us. - auto shptrit = shapes_ptr.begin(); - for(auto shit = shapes.begin(); shit != shapes.end(); ++shit, ++shptrit) - { - // Try to place items to the center - Item& itm = *shit; - auto ibb = itm.boundingBox(); - auto d = binbb.center() - ibb.center(); - itm.translate(d); - if(!is_colliding(itm)) { - preload(itm); - - auto offset = itm.translation(); - Radians rot = itm.rotation(); - ModelInstance *minst = *shptrit; - Vec3d foffset(offset.X*SCALING_FACTOR, - offset.Y*SCALING_FACTOR, - minst->get_offset()(Z)); - - // write the transformation data into the model instance - minst->set_rotation(Z, rot); - minst->set_offset(foffset); - - shit = shapes.erase(shit); - shptrit = shapes_ptr.erase(shptrit); - break; - } - } - }; - - switch(bedhint.type) { - case BedShapeType::BOX: { - - // Create the arranger for the box shaped bed - AutoArranger arrange(binbb, min_obj_distance); - - if(!preshapes.front().empty()) { // If there is something on the plate - arrange.preload(preshapes); - try_first_to_center( - [&arrange](const Item& itm) {return arrange.is_colliding(itm);}, - [&arrange](Item& itm) { arrange.preload({{itm}}); } - ); - } - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); - break; - } - case BedShapeType::CIRCLE: { - - auto c = bedhint.shape.circ; - auto cc = to_lnCircle(c); - - // Create the arranger for the box shaped bed - AutoArranger arrange(cc, min_obj_distance); - - if(!preshapes.front().empty()) { // If there is something on the plate - arrange.preload(preshapes); - try_first_to_center( - [&arrange](const Item& itm) {return arrange.is_colliding(itm);}, - [&arrange](Item& itm) { arrange.preload({{itm}}); } - ); - } - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); - break; - } - case BedShapeType::IRREGULAR: - case BedShapeType::WHO_KNOWS: { - using P = libnest2d::PolygonImpl; - - auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); - P irrbed = sl::create(std::move(ctour)); - - AutoArranger

arrange(irrbed, min_obj_distance); - - if(!preshapes.front().empty()) { // If there is something on the plate - arrange.preload(preshapes); - try_first_to_center( - [&arrange](const Item& itm) {return arrange.is_colliding(itm);}, - [&arrange](Item& itm) { arrange.preload({{itm}}); } - ); - } - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); - break; - } - }; - - // Now we go through the result which will contain the fixed and the moving - // polygons as well. We will have to search for our item. - - const auto STRIDE_PADDING = 1.2; - Coord stride = Coord(STRIDE_PADDING*binbb.width()*SCALING_FACTOR); - Coord batch_offset = 0; - - for(auto& group : result) { - for(auto& r : group) if(r.first < shapes.size()) { - Item& resultitem = r.second; - unsigned idx = r.first; - auto offset = resultitem.translation(); - Radians rot = resultitem.rotation(); - ModelInstance *minst = shapes_ptr[idx]; - Vec3d foffset(offset.X*SCALING_FACTOR + batch_offset, - offset.Y*SCALING_FACTOR, - minst->get_offset()(Z)); - - // write the transformation data into the model instance - minst->set_rotation(Z, rot); - minst->set_offset(foffset); - } - batch_offset += stride; - } -} - -} - - -} diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp deleted file mode 100644 index b61443da0..000000000 --- a/src/libslic3r/ModelArrange.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef MODELARRANGE_HPP -#define MODELARRANGE_HPP - -#include "Model.hpp" - -namespace Slic3r { - -class Model; - -namespace arr { - -class Circle { - Point center_; - double radius_; -public: - - inline Circle(): center_(0, 0), radius_(std::nan("")) {} - inline Circle(const Point& c, double r): center_(c), radius_(r) {} - - inline double radius() const { return radius_; } - inline const Point& center() const { return center_; } - inline operator bool() { return !std::isnan(radius_); } -}; - -enum class BedShapeType { - BOX, - CIRCLE, - IRREGULAR, - WHO_KNOWS -}; - -struct BedShapeHint { - BedShapeType type; - /*union*/ struct { // I know but who cares... - Circle circ; - BoundingBox box; - Polyline polygon; - } shape; -}; - -BedShapeHint bedShape(const Polyline& bed); - -struct WipeTowerInfo { - bool is_wipe_tower = false; - Vec2d pos; - Vec2d bb_size; - double rotation; -}; - -/** - * \brief Arranges the model objects on the screen. - * - * The arrangement considers multiple bins (aka. print beds) for placing all - * the items provided in the model argument. If the items don't fit on one - * print bed, the remaining will be placed onto newly created print beds. - * The first_bin_only parameter, if set to true, disables this behavior and - * makes sure that only one print bed is filled and the remaining items will be - * untouched. When set to false, the items which could not fit onto the - * print bed will be placed next to the print bed so the user should see a - * pile of items on the print bed and some other piles outside the print - * area that can be dragged later onto the print bed as a group. - * - * \param model The model object with the 3D content. - * \param dist The minimum distance which is allowed for any pair of items - * on the print bed in any direction. - * \param bb The bounding box of the print bed. It corresponds to the 'bin' - * for bin packing. - * \param first_bin_only This parameter controls whether to place the - * remaining items which do not fit onto the print area next to the print - * bed or leave them untouched (let the user arrange them by hand or remove - * them). - * \param progressind Progress indicator callback called when an object gets - * packed. The unsigned argument is the number of items remaining to pack. - * \param stopcondition A predicate returning true if abort is needed. - */ -bool arrange(Model &model, - WipeTowerInfo& wipe_tower_info, - coord_t min_obj_distance, - const Slic3r::Polyline& bed, - BedShapeHint bedhint, - bool first_bin_only, - std::function progressind, - std::function stopcondition); - -/// This will find a suitable position for a new object instance and leave the -/// old items untouched. -void find_new_position(const Model& model, - ModelInstancePtrs instances_to_add, - coord_t min_obj_distance, - const Slic3r::Polyline& bed, - WipeTowerInfo& wti); - -} // arr -} // Slic3r -#endif // MODELARRANGE_HPP diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 7ef15d2a4..7388ec219 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3429,36 +3429,24 @@ void GLCanvas3D::update_ui_from_settings() -arr::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const +GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const { - arr::WipeTowerInfo wti; + WipeTowerInfo wti; + for (const GLVolume* vol : m_volumes.volumes) { if (vol->is_wipe_tower) { - wti.is_wipe_tower = true; - wti.pos = Vec2d(m_config->opt_float("wipe_tower_x"), + wti.m_pos = Vec2d(m_config->opt_float("wipe_tower_x"), m_config->opt_float("wipe_tower_y")); - wti.rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); + wti.m_rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); const BoundingBoxf3& bb = vol->bounding_box(); - wti.bb_size = Vec2d(bb.size()(0), bb.size()(1)); + wti.m_bb_size = Vec2d(bb.size().x(), bb.size().y()); break; } } + return wti; } - -void GLCanvas3D::arrange_wipe_tower(const arr::WipeTowerInfo& wti) const -{ - if (wti.is_wipe_tower) { - DynamicPrintConfig cfg; - cfg.opt("wipe_tower_x", true)->value = wti.pos(0); - cfg.opt("wipe_tower_y", true)->value = wti.pos(1); - cfg.opt("wipe_tower_rotation_angle", true)->value = (180./M_PI) * wti.rotation; - wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); - } -} - - Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) { float z0 = 0.0f; @@ -5745,5 +5733,14 @@ const SLAPrint* GLCanvas3D::sla_print() const return (m_process == nullptr) ? nullptr : m_process->sla_print(); } +void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const +{ + DynamicPrintConfig cfg; + cfg.opt("wipe_tower_x", true)->value = m_pos(X); + cfg.opt("wipe_tower_y", true)->value = m_pos(Y); + cfg.opt("wipe_tower_rotation_angle", true)->value = (180./M_PI) * m_rotation; + wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 16773b2b9..2ce35660a 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -4,7 +4,6 @@ #include #include -#include "libslic3r/ModelArrange.hpp" #include "3DScene.hpp" #include "GLToolbar.hpp" #include "Event.hpp" @@ -626,9 +625,28 @@ public: int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; } int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } - - arr::WipeTowerInfo get_wipe_tower_info() const; - void arrange_wipe_tower(const arr::WipeTowerInfo& wti) const; + + class WipeTowerInfo { + protected: + Vec2d m_pos = {std::nan(""), std::nan("")}; + Vec2d m_bb_size = {0., 0.}; + double m_rotation = 0.; + friend class GLCanvas3D; + public: + + inline operator bool() const + { + return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y()); + } + + inline const Vec2d& pos() const { return m_pos; } + inline double rotation() const { return m_rotation; } + inline const Vec2d bb_size() const { return m_bb_size; } + + void apply_wipe_tower() const; + }; + + WipeTowerInfo get_wipe_tower_info() const; // Returns the view ray line, in world coordinate, at the given mouse position. Linef3 mouse_ray(const Point& mouse_pos); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index f7abb4128..429cc18ee 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -359,9 +359,7 @@ DynamicPrintConfig& ObjectList::get_item_config(const wxDataViewItem& item) cons assert(item); const ItemType type = m_objects_model->GetItemType(item); - const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : - m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); - + const int obj_idx = m_objects_model->GetObjectIdByItem(item); const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; assert(obj_idx >= 0 || ((type & itVolume) && vol_idx >=0)); @@ -1037,12 +1035,17 @@ void ObjectList::get_settings_choice(const wxString& category_name) { wxArrayString names; wxArrayInt selections; + wxDataViewItem item = GetSelection(); settings_menu_hierarchy settings_menu; - const bool is_part = m_objects_model->GetParent(GetSelection()) != wxDataViewItem(0); + const bool is_part = m_objects_model->GetItemType(item) & (itVolume | itLayer); get_options_menu(settings_menu, is_part); std::vector< std::pair > *settings_list = nullptr; + if (!m_config) + m_config = &get_item_config(item); + + assert(m_config); auto opt_keys = m_config->keys(); for (auto& cat : settings_menu) @@ -1144,27 +1147,33 @@ void ObjectList::get_settings_choice(const wxString& category_name) // Add settings item for object/sub-object and show them - show_settings(add_settings_item(GetSelection(), m_config)); + if (!(m_objects_model->GetItemType(item) & (itObject | itVolume | itLayer))) + item = m_objects_model->GetTopParent(item); + show_settings(add_settings_item(item, m_config)); } void ObjectList::get_freq_settings_choice(const wxString& bundle_name) { std::vector options = get_options_for_bundle(bundle_name); + wxDataViewItem item = GetSelection(); /* Because of we couldn't edited layer_height for ItVolume from settings list, * correct options according to the selected item type : * remove "layer_height" option */ - if ((m_objects_model->GetItemType(GetSelection()) & itVolume) && bundle_name == _("Layers and Perimeters")) { + if ((m_objects_model->GetItemType(item) & itVolume) && bundle_name == _("Layers and Perimeters")) { const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height"); if (layer_height_it != options.end()) options.erase(layer_height_it); } + if (!m_config) + m_config = &get_item_config(item); + assert(m_config); auto opt_keys = m_config->keys(); - take_snapshot(wxString::Format(_(L("Add Settings Bundle for %s")), m_objects_model->GetItemType(GetSelection()) & itObject ? _(L("Object")) : _(L("Sub-object")))); + take_snapshot(wxString::Format(_(L("Add Settings Bundle for %s")), m_objects_model->GetItemType(item) & (itVolume|itLayer) ? _(L("Sub-object")) : _(L("Object")))); const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; for (auto& opt_key : options) @@ -1181,7 +1190,9 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name) } // Add settings item for object/sub-object and show them - show_settings(add_settings_item(GetSelection(), m_config)); + if (!(m_objects_model->GetItemType(item) & (itObject | itVolume | itLayer))) + item = m_objects_model->GetTopParent(item); + show_settings(add_settings_item(item, m_config)); } void ObjectList::show_settings(const wxDataViewItem settings_item) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f05def49d..a9972d192 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -31,7 +32,6 @@ #include "libslic3r/Format/3mf.hpp" #include "libslic3r/GCode/PreviewData.hpp" #include "libslic3r/Model.hpp" -#include "libslic3r/ModelArrange.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/PrintConfig.hpp" @@ -1355,6 +1355,44 @@ struct Plater::priv BackgroundSlicingProcess background_process; bool suppressed_backround_processing_update { false }; + // Cache the wti info + class WipeTower: public GLCanvas3D::WipeTowerInfo { + using ArrangePolygon = arrangement::ArrangePolygon; + friend priv; + public: + + void apply_arrange_result(const Vec2crd& tr, double rotation) + { + m_pos = unscaled(tr); m_rotation = rotation; + apply_wipe_tower(); + } + + ArrangePolygon get_arrange_polygon() const + { + Polygon p({ + {coord_t(0), coord_t(0)}, + {scaled(m_bb_size(X)), coord_t(0)}, + {scaled(m_bb_size)}, + {coord_t(0), scaled(m_bb_size(Y))}, + {coord_t(0), coord_t(0)}, + }); + + ArrangePolygon ret; + ret.poly.contour = std::move(p); + ret.translation = scaled(m_pos); + ret.rotation = m_rotation; + return ret; + } + } wipetower; + + WipeTower& updated_wipe_tower() { + auto wti = view3D->get_canvas3d()->get_wipe_tower_info(); + wipetower.m_pos = wti.pos(); + wipetower.m_rotation = wti.rotation(); + wipetower.m_bb_size = wti.bb_size(); + return wipetower; + } + // A class to handle UI jobs like arranging and optimizing rotation. // These are not instant jobs, the user has to be informed about their // state in the status progress indicator. On the other hand they are @@ -1365,147 +1403,280 @@ struct Plater::priv // objects would be frozen for the user. In case of arrange, an animation // could be shown, or with the optimize orientations, partial results // could be displayed. - class Job: public wxEvtHandler { - int m_range = 100; + class Job : public wxEvtHandler + { + int m_range = 100; std::future m_ftr; - priv *m_plater = nullptr; - std::atomic m_running {false}, m_canceled {false}; - bool m_finalized = false; - - void run() { - m_running.store(true); process(); m_running.store(false); - + priv * m_plater = nullptr; + std::atomic m_running{false}, m_canceled{false}; + bool m_finalized = false; + + void run() + { + m_running.store(true); + process(); + m_running.store(false); + // ensure to call the last status to finalize the job update_status(status_range(), ""); } - + protected: - // status range for a particular job virtual int status_range() const { return 100; } - + // status update, to be used from the work thread (process() method) - void update_status(int st, const wxString& msg = "") { - auto evt = new wxThreadEvent(); evt->SetInt(st); evt->SetString(msg); - wxQueueEvent(this, evt); + void update_status(int st, const wxString &msg = "") + { + auto evt = new wxThreadEvent(); + evt->SetInt(st); + evt->SetString(msg); + wxQueueEvent(this, evt); } - - priv& plater() { return *m_plater; } - bool was_canceled() const { return m_canceled.load(); } - + + priv & plater() { return *m_plater; } + const priv &plater() const { return *m_plater; } + bool was_canceled() const { return m_canceled.load(); } + // Launched just before start(), a job can use it to prepare internals virtual void prepare() {} - - // Launched when the job is finished. It refreshes the 3dscene by def. - virtual void finalize() { + + // Launched when the job is finished. It refreshes the 3Dscene by def. + virtual void finalize() + { // Do a full refresh of scene tree, including regenerating // all the GLVolumes. FIXME The update function shall just // reload the modified matrices. - if(! was_canceled()) - plater().update(true); + if (!was_canceled()) plater().update(true); } - + public: - - Job(priv *_plater): m_plater(_plater) + Job(priv *_plater) : m_plater(_plater) { - Bind(wxEVT_THREAD, [this](const wxThreadEvent& evt){ + Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { auto msg = evt.GetString(); - if(! msg.empty()) plater().statusbar()->set_status_text(msg); - - if(m_finalized) return; - + if (!msg.empty()) + plater().statusbar()->set_status_text(msg); + + if (m_finalized) return; + plater().statusbar()->set_progress(evt.GetInt()); - if(evt.GetInt() == status_range()) { - + if (evt.GetInt() == status_range()) { // set back the original range and cancel callback plater().statusbar()->set_range(m_range); plater().statusbar()->set_cancel_callback(); wxEndBusyCursor(); - + finalize(); - + // dont do finalization again for the same process m_finalized = true; } }); } - - // TODO: use this when we all migrated to VS2019 - // Job(const Job&) = delete; - // Job(Job&&) = default; - // Job& operator=(const Job&) = delete; - // Job& operator=(Job&&) = default; - Job(const Job&) = delete; - Job& operator=(const Job&) = delete; - Job(Job &&o) : - m_range(o.m_range), - m_ftr(std::move(o.m_ftr)), - m_plater(o.m_plater), - m_finalized(o.m_finalized) - { - m_running.store(o.m_running.load()); - m_canceled.store(o.m_canceled.load()); - } - + + Job(const Job &) = delete; + Job(Job &&) = default; + Job &operator=(const Job &) = delete; + Job &operator=(Job &&) = default; + virtual void process() = 0; - - void start() { // Start the job. No effect if the job is already running - if(! m_running.load()) { - - prepare(); - + + void start() + { // Start the job. No effect if the job is already running + if (!m_running.load()) { + prepare(); + // Save the current status indicatior range and push the new one m_range = plater().statusbar()->get_range(); plater().statusbar()->set_range(status_range()); - + // init cancellation flag and set the cancel callback m_canceled.store(false); - plater().statusbar()->set_cancel_callback( [this](){ - m_canceled.store(true); - }); - + plater().statusbar()->set_cancel_callback( + [this]() { m_canceled.store(true); }); + m_finalized = false; - + // Changing cursor to busy wxBeginBusyCursor(); - - try { // Execute the job + + try { // Execute the job m_ftr = std::async(std::launch::async, &Job::run, this); - } catch(std::exception& ) { - update_status(status_range(), - _(L("ERROR: not enough resources to execute a new job."))); + } catch (std::exception &) { + update_status(status_range(), + _(L("ERROR: not enough resources to " + "execute a new job."))); } - + // The state changes will be undone when the process hits the // last status value, in the status update handler (see ctor) } } - - // To wait for the running job and join the threads. False is returned - // if the timeout has been reached and the job is still running. Call - // cancel() before this fn if you want to explicitly end the job. - bool join(int timeout_ms = 0) const { - if(!m_ftr.valid()) return true; - - if(timeout_ms <= 0) + + // To wait for the running job and join the threads. False is + // returned if the timeout has been reached and the job is still + // running. Call cancel() before this fn if you want to explicitly + // end the job. + bool join(int timeout_ms = 0) const + { + if (!m_ftr.valid()) return true; + + if (timeout_ms <= 0) m_ftr.wait(); - else if(m_ftr.wait_for(std::chrono::milliseconds(timeout_ms)) == - std::future_status::timeout) + else if (m_ftr.wait_for(std::chrono::milliseconds( + timeout_ms)) == std::future_status::timeout) return false; - + return true; } - + bool is_running() const { return m_running.load(); } void cancel() { m_canceled.store(true); } }; - + enum class Jobs : size_t { Arrange, Rotoptimize }; + class ArrangeJob : public Job + { + using ArrangePolygon = arrangement::ArrangePolygon; + using ArrangePolygons = arrangement::ArrangePolygons; + + // The gap between logical beds in the x axis expressed in ratio of + // the current bed width. + static const constexpr double LOGICAL_BED_GAP = 1. / 5.; + + ArrangePolygons m_selected, m_unselected; + + // clear m_selected and m_unselected, reserve space for next usage + void clear_input() { + const Model &model = plater().model; + + size_t count = 0; // To know how much space to reserve + for (auto obj : model.objects) count += obj->instances.size(); + m_selected.clear(), m_unselected.clear(); + m_selected.reserve(count + 1 /* for optional wti */); + m_unselected.reserve(count + 1 /* for optional wti */); + } + + // Stride between logical beds + coord_t bed_stride() const { + double bedwidth = plater().bed_shape_bb().size().x(); + return scaled((1. + LOGICAL_BED_GAP) * bedwidth); + } + + // Set up arrange polygon for a ModelInstance and Wipe tower + template ArrangePolygon get_arrange_poly(T *obj) const { + ArrangePolygon ap = obj->get_arrange_polygon(); + ap.priority = 0; + ap.bed_idx = ap.translation.x() / bed_stride(); + ap.setter = [obj, this](const ArrangePolygon &p) { + if (p.is_arranged()) { + auto t = p.translation; t.x() += p.bed_idx * bed_stride(); + obj->apply_arrange_result(t, p.rotation); + } + }; + return ap; + } + + // Prepare all objects on the bed regardless of the selection + void prepare_all() { + clear_input(); + + for (ModelObject *obj: plater().model.objects) + for (ModelInstance *mi : obj->instances) + m_selected.emplace_back(get_arrange_poly(mi)); + + auto& wti = plater().updated_wipe_tower(); + if (wti) m_selected.emplace_back(get_arrange_poly(&wti)); + } + + // Prepare the selected and unselected items separately. If nothing is + // selected, behaves as if everything would be selected. + void prepare_selected() { + clear_input(); + + Model &model = plater().model; + coord_t stride = bed_stride(); + + std::vector + obj_sel(model.objects.size(), nullptr); + + for (auto &s : plater().get_selection().get_content()) + if (s.first < int(obj_sel.size())) obj_sel[s.first] = &s.second; + + // Go through the objects and check if inside the selection + for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { + const Selection::InstanceIdxsList * instlist = obj_sel[oidx]; + ModelObject *mo = model.objects[oidx]; + + std::vector inst_sel(mo->instances.size(), false); + + if (instlist) + for (auto inst_id : *instlist) inst_sel[inst_id] = true; + + for (size_t i = 0; i < inst_sel.size(); ++i) { + ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]); + + inst_sel[i] ? + m_selected.emplace_back(std::move(ap)) : + m_unselected.emplace_back(std::move(ap)); + } + } + + auto& wti = plater().updated_wipe_tower(); + if (wti) { + ArrangePolygon &&ap = get_arrange_poly(&wti); + + plater().get_selection().is_wipe_tower() ? + m_selected.emplace_back(std::move(ap)) : + m_unselected.emplace_back(std::move(ap)); + } + + // If the selection was empty arrange everything + if (m_selected.empty()) m_selected.swap(m_unselected); + + // The strides have to be removed from the fixed items. For the + // arrangeable (selected) items bed_idx is ignored and the + // translation is irrelevant. + for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride; + } + + protected: + + void prepare() override + { + wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all(); + } + + public: + using Job::Job; + + int status_range() const override { return int(m_selected.size()); } + + void process() override; + + void finalize() override { + // Ignore the arrange result if aborted. + if (was_canceled()) return; + + // Apply the arrange result to all selected objects + for (ArrangePolygon &ap : m_selected) ap.apply(); + + plater().update(false /*dont force_full_scene_refresh*/); + } + }; + + class RotoptimizeJob : public Job + { + public: + using Job::Job; + void process() override; + }; + // Jobs defined inside the group class will be managed so that only one can // run at a time. Also, the background process will be stopped if a job is // started. @@ -1515,49 +1686,19 @@ struct Plater::priv priv * m_plater; - class ArrangeJob : public Job - { - int count = 0; - - protected: - void prepare() override - { - count = 0; - for (auto obj : plater().model.objects) - count += int(obj->instances.size()); - } - - public: - //using Job::Job; - ArrangeJob(priv * pltr): Job(pltr) {} - int status_range() const override { return count; } - void set_count(int c) { count = c; } - void process() override; - } arrange_job/*{m_plater}*/; - - class RotoptimizeJob : public Job - { - public: - //using Job::Job; - RotoptimizeJob(priv * pltr): Job(pltr) {} - void process() override; - } rotoptimize_job/*{m_plater}*/; + ArrangeJob arrange_job{m_plater}; + RotoptimizeJob rotoptimize_job{m_plater}; // To create a new job, just define a new subclass of Job, implement // the process and the optional prepare() and finalize() methods // Register the instance of the class in the m_jobs container // if it cannot run concurrently with other jobs in this group - std::vector> m_jobs/*{arrange_job, - rotoptimize_job}*/; + std::vector> m_jobs{arrange_job, + rotoptimize_job}; public: - ExclusiveJobGroup(priv *_plater) - : m_plater(_plater) - , arrange_job(m_plater) - , rotoptimize_job(m_plater) - , m_jobs({arrange_job, rotoptimize_job}) - {} + ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {} void start(Jobs jid) { m_plater->background_process.stop(); @@ -1618,6 +1759,9 @@ struct Plater::priv std::string get_config(const std::string &key) const; BoundingBoxf bed_shape_bb() const; BoundingBox scaled_bed_shape_bb() const; + arrangement::BedShapeHint get_bed_shape_hint() const; + + void find_new_position(const ModelInstancePtrs &instances, coord_t min_d); std::vector load_files(const std::vector& input_files, bool load_model, bool load_config); std::vector load_model_objects(const ModelObjectPtrs &model_objects); wxString get_export_file(GUI::FileType file_type); @@ -2239,9 +2383,9 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode auto& bedpoints = bed_shape_opt->values; Polyline bed; bed.points.reserve(bedpoints.size()); for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - - arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); - + + std::pair wti = view3D->get_canvas3d()->get_wipe_tower_info(); + arr::find_new_position(model, new_instances, min_obj_distance, bed, wti); // it remains to move the wipe tower: @@ -2480,71 +2624,82 @@ void Plater::priv::sla_optimize_rotation() { m_ui_jobs.start(Jobs::Rotoptimize); } -void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { - // TODO: we should decide whether to allow arrange when the search is - // running we should probably disable explicit slicing and background - // processing +arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const { + + const auto *bed_shape_opt = config->opt("bed_shape"); + assert(bed_shape_opt); + + if (!bed_shape_opt) return {}; + + auto &bedpoints = bed_shape_opt->values; + Polyline bedpoly; bedpoly.points.reserve(bedpoints.size()); + for (auto &v : bedpoints) bedpoly.append(scaled(v)); + + return arrangement::BedShapeHint(bedpoly); +} +void Plater::priv::find_new_position(const ModelInstancePtrs &instances, + coord_t min_d) +{ + arrangement::ArrangePolygons movable, fixed; + + for (const ModelObject *mo : model.objects) + for (const ModelInstance *inst : mo->instances) { + auto it = std::find(instances.begin(), instances.end(), inst); + auto arrpoly = inst->get_arrange_polygon(); + + if (it == instances.end()) + fixed.emplace_back(std::move(arrpoly)); + else + movable.emplace_back(std::move(arrpoly)); + } + + if (updated_wipe_tower()) + fixed.emplace_back(wipetower.get_arrange_polygon()); + + arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint()); + + for (size_t i = 0; i < instances.size(); ++i) + if (movable[i].bed_idx == 0) + instances[i]->apply_arrange_result(movable[i].translation, + movable[i].rotation); +} + +void Plater::priv::ArrangeJob::process() { static const auto arrangestr = _(L("Arranging")); - - auto &config = plater().config; - auto &view3D = plater().view3D; - auto &model = plater().model; - + // FIXME: I don't know how to obtain the minimum distance, it depends // on printer technology. I guess the following should work but it crashes. double dist = 6; // PrintConfig::min_object_distance(config); if (plater().printer_technology == ptFFF) { - dist = PrintConfig::min_object_distance(config); + dist = PrintConfig::min_object_distance(plater().config); } - - auto min_obj_distance = coord_t(dist / SCALING_FACTOR); - - const auto *bed_shape_opt = config->opt( - "bed_shape"); - - assert(bed_shape_opt); - auto & bedpoints = bed_shape_opt->values; - Polyline bed; - bed.points.reserve(bedpoints.size()); - for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - - update_status(0, arrangestr); - - arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); - + + coord_t min_d = scaled(dist); + auto count = unsigned(m_selected.size()); + arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); + try { - arr::BedShapeHint hint; - - // TODO: from Sasha from GUI or - hint.type = arr::BedShapeType::WHO_KNOWS; - - arr::arrange(model, - wti, - min_obj_distance, - bed, - hint, - false, // create many piles not just one pile - [this](unsigned st) { - if (st > 0) - update_status(count - int(st), arrangestr); - }, - [this]() { return was_canceled(); }); + arrangement::arrange(m_selected, m_unselected, min_d, bedshape, + [this, count](unsigned st) { + if (st > + 0) // will not finalize after last one + update_status(count - st, arrangestr); + }, + [this]() { return was_canceled(); }); } catch (std::exception & /*e*/) { GUI::show_error(plater().q, - L("Could not arrange model objects! " - "Some geometries may be invalid.")); + _(L("Could not arrange model objects! " + "Some geometries may be invalid."))); } - - update_status(count, + + // finalize just here. + update_status(int(count), was_canceled() ? _(L("Arranging canceled.")) : _(L("Arranging done."))); - - // it remains to move the wipe tower: - view3D->get_canvas3d()->arrange_wipe_tower(wti); } -void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() +void Plater::priv::RotoptimizeJob::process() { int obj_idx = plater().get_selected_object_idx(); if (obj_idx < 0) { return; } @@ -2561,15 +2716,6 @@ void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() }, [this]() { return was_canceled(); }); - const auto *bed_shape_opt = - plater().config->opt("bed_shape"); - - assert(bed_shape_opt); - - auto & bedpoints = bed_shape_opt->values; - Polyline bed; - bed.points.reserve(bedpoints.size()); - for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); double mindist = 6.0; // FIXME @@ -2590,14 +2736,9 @@ void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() oi->set_rotation(rt); } - - arr::WipeTowerInfo wti; // useless in SLA context - arr::find_new_position(plater().model, - o->instances, - coord_t(mindist / SCALING_FACTOR), - bed, - wti); - + + plater().find_new_position(o->instances, scaled(mindist)); + // Correct the z offset of the object which was corrupted be // the rotation o->ensure_on_bed(); @@ -3486,7 +3627,7 @@ void Plater::priv::set_bed_shape(const Pointfs& shape) bool Plater::priv::can_delete() const { - return !get_selection().is_empty() && !get_selection().is_wipe_tower(); + return !get_selection().is_empty() && !get_selection().is_wipe_tower() && !m_ui_jobs.is_any_running(); } bool Plater::priv::can_delete_all() const diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 7e8a2d92d..6300ada31 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -552,6 +552,29 @@ void ObjectDataViewModelNode::msw_rescale() update_settings_digest_bitmaps(); } +bool ObjectDataViewModelNode::SetValue(const wxVariant& variant, unsigned col) +{ + switch (col) + { + case 0: { + DataViewBitmapText data; + data << variant; + m_bmp = data.GetBitmap(); + m_name = data.GetText(); + return true; } + case 1: { + const wxString & val = variant.GetString(); + m_extruder = val == "0" ? _(L("default")) : val; + return true; } + case 2: + m_action_icon << variant; + return true; + default: + printf("MyObjectTreeModel::SetValue: wrong column"); + } + return false; +} + void ObjectDataViewModelNode::SetIdx(const int& idx) { m_idx = idx; diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index d0edf9760..9a6460dcb 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -301,27 +301,7 @@ public: return m_children.GetCount(); } - bool SetValue(const wxVariant &variant, unsigned int col) - { - switch (col) - { - case 0:{ - DataViewBitmapText data; - data << variant; - m_bmp = data.GetBitmap(); - m_name = data.GetText(); - return true;} - case 1: - m_extruder = variant.GetString(); - return true; - case 2: - m_action_icon << variant; - return true; - default: - printf("MyObjectTreeModel::SetValue: wrong column"); - } - return false; - } + bool SetValue(const wxVariant &variant, unsigned int col); void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } const wxBitmap& GetBitmap() const { return m_bmp; }