From ffc369e187dcad387dd64cf8db35537609beb6ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Mon, 11 Nov 2024 12:15:19 +0100 Subject: [PATCH] Arrange: Remove old arrange --- src/libslic3r/Arrange.cpp | 780 ----------------------------- src/libslic3r/Arrange.hpp | 220 -------- src/libslic3r/CMakeLists.txt | 1 - src/slic3r/GUI/Jobs/ArrangeJob.cpp | 459 ----------------- src/slic3r/GUI/Jobs/ArrangeJob.hpp | 122 ----- src/slic3r/GUI/Jobs/FillBedJob.cpp | 207 -------- src/slic3r/GUI/Jobs/FillBedJob.hpp | 46 -- 7 files changed, 1835 deletions(-) delete mode 100644 src/libslic3r/Arrange.cpp delete mode 100644 src/libslic3r/Arrange.hpp delete mode 100644 src/slic3r/GUI/Jobs/ArrangeJob.cpp delete mode 100644 src/slic3r/GUI/Jobs/ArrangeJob.hpp delete mode 100644 src/slic3r/GUI/Jobs/FillBedJob.cpp delete mode 100644 src/slic3r/GUI/Jobs/FillBedJob.hpp diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp deleted file mode 100644 index 22bb794f4e..0000000000 --- a/src/libslic3r/Arrange.cpp +++ /dev/null @@ -1,780 +0,0 @@ -///|/ Copyright (c) Prusa Research 2018 - 2023 Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966 -///|/ -///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher -///|/ -#include "Arrange.hpp" - -#include "BoundingBox.hpp" - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -#if defined(_MSC_VER) && defined(__clang__) -#define BOOST_NO_CXX17_HDR_STRING_VIEW -#endif - -#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 constexpr Eigen::Matrix unscaled( - const Slic3r::ClipperLib::IntPoint &v) noexcept -{ - return Eigen::Matrix{unscaled(v.x()), - unscaled(v.y())}; -} - -namespace arrangement { - -using namespace libnest2d; - -// Get the libnest2d types for clipper backend -using Item = _Item; -using Box = _Box; -using Circle = _Circle; -using Segment = _Segment; -using MultiPolygon = ExPolygons; - -// 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 fill_config(PConf& pcfg, const ArrangeParams ¶ms) { - - // Align the arranged pile into the center of the bin - switch (params.alignment) { - case Pivots::Center: pcfg.alignment = PConf::Alignment::CENTER; break; - case Pivots::BottomLeft: pcfg.alignment = PConf::Alignment::BOTTOM_LEFT; break; - case Pivots::BottomRight: pcfg.alignment = PConf::Alignment::BOTTOM_RIGHT; break; - case Pivots::TopLeft: pcfg.alignment = PConf::Alignment::TOP_LEFT; break; - case Pivots::TopRight: pcfg.alignment = PConf::Alignment::TOP_RIGHT; break; - } - - // 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. - if (params.allow_rotations) - pcfg.rotations = {0., PI / 2., PI, 3. * PI / 2. }; - else - pcfg.rotations = {0.}; - - // The accuracy of optimization. - // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = params.accuracy; - - // Allow parallel execution. - pcfg.parallel = params.parallel; -} - -// Apply penalty to object function result. This is used only when alignment -// after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN) -// Also, this will only work well for Box shaped beds. -static 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; - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4244) -#pragma warning(disable: 4267) -#endif - SpatIndex m_rtree; // spatial index for the normal (bigger) objects - SpatIndex m_smallsrtree; // spatial index for only the smaller items -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - 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 - ItemGroup m_items; // allready packed items - size_t m_item_count = 0; // Number of all 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 Point &bincenter) - { - const double bin_area = m_bin_area; - const SpatIndex& spatindex = m_rtree; - const SpatIndex& smalls_spatindex = m_smallsrtree; - - // 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 && !m_remaining.empty()) compute_case = BIG_ITEM; - else if (bigitems && m_remaining.empty()) compute_case = LAST_BIG_ITEM; - else compute_case = SMALL_ITEM; - - switch (compute_case) { - case BIG_ITEM: { - const Point& minc = ibb.minCorner(); // bottom left corner - const Point& maxc = ibb.maxCorner(); // top right corner - - // top left and bottom right corners - Point top_left{getX(minc), getY(maxc)}; - Point 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 - boost::container::small_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())); - double R = double(m_remaining.size()) / m_item_count; - - // 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.50 * dist + 0.50 * density; - else - // Let the density matter more when fewer objects remain - score = 0.50 * dist + (1.0 - R) * 0.20 * density + - 0.30 * alignment_score; - - break; - } - case LAST_BIG_ITEM: { - score = norm(pl::distance(ibb.center(), m_pilebb.center())); - 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, - const ArrangeParams ¶ms, - std::function progressind, - std::function stopcond) - : m_pck(bin, params.min_obj_distance) - , m_bin(bin) - , m_bin_area(sl::area(bin)) - , m_norm(std::sqrt(m_bin_area)) - { - fill_config(m_pconf, params); - - // 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(); - - m_pconf.on_preload = [this](const ItemGroup &items, PConfig &cfg) { - if (items.empty()) return; - - cfg.alignment = PConfig::Alignment::DONT_ALIGN; - auto bb = sl::boundingBox(m_bin); - auto bbcenter = bb.center(); - cfg.object_function = [this, bb, bbcenter](const Item &item) { - return fixed_overfit(objfunc(item, bbcenter), bb); - }; - }; - - auto on_packed = params.on_packed; - - if (progressind || on_packed) - m_pck.progressIndicator([this, progressind, on_packed](unsigned rem) { - - if (progressind) - progressind(rem); - - if (on_packed) { - int last_bed = m_pck.lastPackedBinId(); - if (last_bed >= 0) { - Item &last_packed = m_pck.lastResult()[last_bed].back(); - ArrangePolygon ap; - ap.bed_idx = last_packed.binId(); - ap.priority = last_packed.priority(); - on_packed(ap); - } - } - }); - - if (stopcond) m_pck.stopCondition(stopcond); - - m_pck.configure(m_pconf); - } - - template inline void operator()(It from, It to) { - m_rtree.clear(); - m_item_count += size_t(to - from); - m_pck.execute(from, to); - m_item_count = 0; - } - - PConfig& config() { return m_pconf; } - const PConfig& config() const { return m_pconf; } - - inline void preload(std::vector& fixeditems) { - for(unsigned idx = 0; idx < fixeditems.size(); ++idx) { - Item& itm = fixeditems[idx]; - itm.markAsFixedInBin(itm.binId()); - } - - m_item_count += fixeditems.size(); - } -}; - -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); - - 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)); - }; -} - -template void remove_large_items(std::vector &items, Bin &&bin) -{ - auto it = items.begin(); - while (it != items.end()) - sl::isInside(it->transformedShape(), bin) ? - ++it : it = items.erase(it); -} - -template Radians min_area_boundingbox_rotation(const S &sh) -{ - return minAreaBoundingBox, boost::rational>(sh) - .angleToX(); -} - -template -Radians fit_into_box_rotation(const S &sh, const _Box> &box) -{ - return fitIntoBoxRotation, boost::rational>(sh, box); -} - -template // Arrange for arbitrary bin type -void _arrange( - std::vector & shapes, - std::vector & excludes, - const BinT & bin, - const ArrangeParams ¶ms, - std::function progressfn, - std::function stopfn) -{ - // Integer ceiling the min distance from the bed perimeters - coord_t md = params.min_obj_distance; - md = md / 2 - params.min_bed_distance; - - auto corrected_bin = bin; - sl::offset(corrected_bin, md); - ArrangeParams mod_params = params; - mod_params.min_obj_distance = 0; - - AutoArranger arranger{corrected_bin, mod_params, progressfn, stopfn}; - - auto infl = coord_t(std::ceil(params.min_obj_distance / 2.0)); - for (Item& itm : shapes) itm.inflate(infl); - for (Item& itm : excludes) itm.inflate(infl); - - remove_large_items(excludes, corrected_bin); - - // 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); - - // Use the minimum bounding box rotation as a starting point. - // TODO: This only works for convex hull. If we ever switch to concave - // polygon nesting, a convex hull needs to be calculated. - if (params.allow_rotations) { - for (auto &itm : shapes) { - itm.rotation(min_area_boundingbox_rotation(itm.rawShape())); - - // If the item is too big, try to find a rotation that makes it fit - if constexpr (std::is_same_v) { - auto bb = itm.boundingBox(); - if (bb.width() >= bin.width() || bb.height() >= bin.height()) - itm.rotate(fit_into_box_rotation(itm.transformedShape(), bin)); - } - } - } - - if (sl::area(corrected_bin) > 0) - arranger(inp.begin(), inp.end()); - else { - for (Item &itm : inp) - itm.binId(BIN_ID_UNSET); - } - - for (Item &itm : inp) itm.inflate(-infl); -} - -inline Box to_nestbin(const BoundingBox &bb) { return Box{{bb.min(X), bb.min(Y)}, {bb.max(X), bb.max(Y)}};} -inline Circle to_nestbin(const CircleBed &c) { return Circle({c.center()(0), c.center()(1)}, c.radius()); } -inline ExPolygon to_nestbin(const Polygon &p) { return ExPolygon{p}; } -inline Box to_nestbin(const InfiniteBed &bed) { return Box::infinite({bed.center.x(), bed.center.y()}); } - -inline coord_t width(const BoundingBox& box) { return box.max.x() - box.min.x(); } -inline coord_t height(const BoundingBox& box) { return box.max.y() - box.min.y(); } -inline double area(const BoundingBox& box) { return double(width(box)) * height(box); } -inline double poly_area(const Points &pts) { return std::abs(Polygon::area(pts)); } -inline double distance_to(const Point& p1, const Point& p2) -{ - double dx = p2.x() - p1.x(); - double dy = p2.y() - p1.y(); - return std::sqrt(dx*dx + dy*dy); -} - -static CircleBed to_circle(const Point ¢er, const Points& points) { - std::vector vertex_distances; - double avg_dist = 0; - - for (const Point& pt : 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 = {}; - break; - } - } - - return ret; -} - -// Create Item from Arrangeable -static void process_arrangeable(const ArrangePolygon &arrpoly, - std::vector & outp) -{ - Polygon p = arrpoly.poly.contour; - const Vec2crd &offs = arrpoly.translation; - double rotation = arrpoly.rotation; - - outp.emplace_back(std::move(p)); - outp.back().rotation(rotation); - outp.back().translation({offs.x(), offs.y()}); - outp.back().inflate(arrpoly.inflation); - outp.back().binId(arrpoly.bed_idx); - outp.back().priority(arrpoly.priority); - outp.back().setOnPackedFn([&arrpoly](Item &itm){ - itm.inflate(-arrpoly.inflation); - }); -} - -template auto call_with_bed(const Points &bed, Fn &&fn) -{ - if (bed.empty()) - return fn(InfiniteBed{}); - else if (bed.size() == 1) - return fn(InfiniteBed{bed.front()}); - else { - auto bb = BoundingBox(bed); - CircleBed circ = to_circle(bb.center(), bed); - auto parea = poly_area(bed); - - if ((1.0 - parea / area(bb)) < 1e-3) - return fn(RectangleBed{bb}); - else if (!std::isnan(circ.radius())) - return fn(circ); - else - return fn(IrregularBed{ExPolygon(bed)}); - } -} - -bool is_box(const Points &bed) -{ - return !bed.empty() && - ((1.0 - poly_area(bed) / area(BoundingBox(bed))) < 1e-3); -} - -template<> -void arrange(ArrangePolygons & items, - const ArrangePolygons &excludes, - const Points & bed, - const ArrangeParams & params) -{ - arrange(items, excludes, to_arrange_bed(bed), params); -} - -template -void arrange(ArrangePolygons & arrangables, - const ArrangePolygons &excludes, - const BedT & bed, - const ArrangeParams & params) -{ - namespace clppr = Slic3r::ClipperLib; - - std::vector items, fixeditems; - items.reserve(arrangables.size()); - - 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 = params.stopcondition; - auto &pri = params.progressind; - - _arrange(items, fixeditems, to_nestbin(bed), params, pri, cfn); - - for(size_t i = 0; i < items.size(); ++i) { - Point 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(); - } -} - -template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms); -template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms); -template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms); -template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms); - -ArrangeBed to_arrange_bed(const Points &bedpts) -{ - ArrangeBed ret; - - call_with_bed(bedpts, [&](const auto &bed) { - ret = bed; - }); - - return ret; -} - -void arrange(ArrangePolygons &items, - const ArrangePolygons &excludes, - const SegmentedRectangleBed &bed, - const ArrangeParams ¶ms) -{ - arrange(items, excludes, bed.bb, params); - - if (! excludes.empty()) - return; - - auto it = std::max_element(items.begin(), items.end(), - [](auto &i1, auto &i2) { - return i1.bed_idx < i2.bed_idx; - }); - - size_t beds = 0; - if (it != items.end()) - beds = it->bed_idx + 1; - - std::vector pilebb(beds); - - for (auto &itm : items) { - if (itm.bed_idx >= 0) - pilebb[itm.bed_idx].merge(get_extents(itm.transformed_poly())); - } - - auto piecesz = unscaled(bed.bb).size(); - piecesz.x() /= bed.segments.x(); - piecesz.y() /= bed.segments.y(); - - for (size_t bedidx = 0; bedidx < beds; ++bedidx) { - BoundingBox bb; - auto pilesz = unscaled(pilebb[bedidx]).size(); - bb.max.x() = scaled(std::ceil(pilesz.x() / piecesz.x()) * piecesz.x()); - bb.max.y() = scaled(std::ceil(pilesz.y() / piecesz.y()) * piecesz.y()); - switch (params.alignment) { - case Pivots::BottomLeft: - bb.translate(bed.bb.min - bb.min); - break; - case Pivots::TopRight: - bb.translate(bed.bb.max - bb.max); - break; - case Pivots::BottomRight: { - Point bedref{bed.bb.max.x(), bed.bb.min.y()}; - Point bbref {bb.max.x(), bb.min.y()}; - bb.translate(bedref - bbref); - break; - } - case Pivots::TopLeft: { - Point bedref{bed.bb.min.x(), bed.bb.max.y()}; - Point bbref {bb.min.x(), bb.max.y()}; - bb.translate(bedref - bbref); - break; - } - case Pivots::Center: { - bb.translate(bed.bb.center() - bb.center()); - break; - } - } - - Vec2crd d = bb.center() - pilebb[bedidx].center(); - - auto bedbb = bed.bb; - bedbb.offset(-params.min_bed_distance); - auto pilebbx = pilebb[bedidx]; - pilebbx.translate(d); - - Point corr{0, 0}; - corr.x() = -std::min(0, pilebbx.min.x() - bedbb.min.x()) - -std::max(0, pilebbx.max.x() - bedbb.max.x()); - corr.y() = -std::min(0, pilebbx.min.y() - bedbb.min.y()) - -std::max(0, pilebbx.max.y() - bedbb.max.y()); - - d += corr; - - for (auto &itm : items) - if (itm.bed_idx == int(bedidx)) - itm.translation += d; - } -} - -BoundingBox bounding_box(const InfiniteBed &bed) -{ - BoundingBox ret; - using C = coord_t; - - // 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 * bed.center.x()) / 4.01); - C My = C((std::numeric_limits::lowest() + 2 * bed.center.y()) / 4.01); - - ret.max = bed.center - Point{Mx, My}; - ret.min = bed.center + Point{Mx, My}; - - return ret; -} - -} // namespace arr -} // namespace Slic3r diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp deleted file mode 100644 index 9718be3fec..0000000000 --- a/src/libslic3r/Arrange.hpp +++ /dev/null @@ -1,220 +0,0 @@ -///|/ Copyright (c) Prusa Research 2018 - 2023 Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966 -///|/ -///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher -///|/ -#ifndef ARRANGE_HPP -#define ARRANGE_HPP - -#include - -#include -#include - -namespace Slic3r { - -class BoundingBox; - -namespace arrangement { - -/// Representing an unbounded bed. -struct InfiniteBed { - Point center; - explicit InfiniteBed(const Point &p = {0, 0}): center{p} {} -}; - -struct RectangleBed { - BoundingBox bb; -}; - -/// A geometry abstraction for a circular print bed. Similarly to BoundingBox. -class CircleBed { - Point center_; - double radius_; -public: - - inline CircleBed(): center_(0, 0), radius_(NaNd) {} - explicit inline CircleBed(const Point& c, double r): center_(c), radius_(r) {} - - inline double radius() const { return radius_; } - inline const Point& center() const { return center_; } -}; - -struct SegmentedRectangleBed { - Vec<2, size_t> segments; - BoundingBox bb; - - SegmentedRectangleBed (const BoundingBox &bb, - size_t segments_x, - size_t segments_y) - : segments{segments_x, segments_y} - , bb{bb} - {} -}; - -struct IrregularBed { - ExPolygon poly; -}; - -//enum BedType { Infinite, Rectangle, Circle, SegmentedRectangle, Irregular }; - -using ArrangeBed = boost::variant; - -BoundingBox bounding_box(const InfiniteBed &bed); -inline BoundingBox bounding_box(const RectangleBed &b) { return b.bb; } -inline BoundingBox bounding_box(const SegmentedRectangleBed &b) { return b.bb; } -inline BoundingBox bounding_box(const CircleBed &b) -{ - auto r = static_cast(std::round(b.radius())); - Point R{r, r}; - - return {b.center() - R, b.center() + R}; -} -inline BoundingBox bounding_box(const ArrangeBed &b) -{ - BoundingBox ret; - auto visitor = [&ret](const auto &b) { ret = bounding_box(b); }; - boost::apply_visitor(visitor, b); - - return ret; -} - -ArrangeBed to_arrange_bed(const Points &bedpts); - -/// 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 - coord_t inflation = 0; /// Arrange with inflated polygon - int bed_idx{UNARRANGED}; /// To which logical bed does poly belong... - int priority{0}; - - // If empty, any rotation is allowed (currently unsupported) - // If only a zero is there, no rotation is allowed - std::vector allowed_rotations = {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; } - - inline ExPolygon transformed_poly() const - { - ExPolygon ret = poly; - ret.rotate(rotation); - ret.translate(translation.x(), translation.y()); - - return ret; - } -}; - -using ArrangePolygons = std::vector; - -enum class Pivots { - Center, TopLeft, BottomLeft, BottomRight, TopRight -}; - -struct ArrangeParams { - - /// The minimum distance which is allowed for any - /// pair of items on the print bed in any direction. - coord_t min_obj_distance = 0; - - /// The minimum distance of any object from bed edges - coord_t min_bed_distance = 0; - - /// The accuracy of optimization. - /// Goes from 0.0 to 1.0 and scales performance as well - float accuracy = 1.f; - - /// Allow parallel execution. - bool parallel = true; - - bool allow_rotations = false; - - /// Final alignment of the merged pile after arrangement - Pivots alignment = Pivots::Center; - - /// Starting position hint for the arrangement - Pivots starting_point = Pivots::Center; - - /// Progress indicator callback called when an object gets packed. - /// The unsigned argument is the number of items remaining to pack. - std::function progressind; - - std::function on_packed; - - /// A predicate returning true if abort is needed. - std::function stopcondition; - - ArrangeParams() = default; - explicit ArrangeParams(coord_t md) : min_obj_distance(md) {} -}; - -/** - * \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. - */ -template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const TBed &bed, const ArrangeParams ¶ms = {}); - -// A dispatch function that determines the bed shape from a set of points. -template<> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Points &bed, const ArrangeParams ¶ms); - -extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms); -extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms); -extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms); -extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms); - -inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const RectangleBed &bed, const ArrangeParams ¶ms) -{ - arrange(items, excludes, bed.bb, params); -} - -inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const IrregularBed &bed, const ArrangeParams ¶ms) -{ - arrange(items, excludes, bed.poly.contour, params); -} - -void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const SegmentedRectangleBed &bed, const ArrangeParams ¶ms); - -inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const ArrangeBed &bed, const ArrangeParams ¶ms) -{ - auto call_arrange = [&](const auto &realbed) { arrange(items, excludes, realbed, params); }; - boost::apply_visitor(call_arrange, bed); -} - -inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } -inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } -inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } -inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } -inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } - -bool is_box(const Points &bed); - -}} // namespace Slic3r::arrangement - -#endif // MODELARRANGE_HPP diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6dfe164e0e..8beb3cd6dd 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -273,7 +273,6 @@ set(SLIC3R_SOURCES MeasureUtils.hpp CustomGCode.cpp CustomGCode.hpp - Arrange/Arrange.hpp Arrange/ArrangeImpl.hpp Arrange/Items/ArrangeItem.hpp Arrange/Items/ArrangeItem.cpp diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp deleted file mode 100644 index 5231b2cab3..0000000000 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ /dev/null @@ -1,459 +0,0 @@ -///|/ Copyright (c) Prusa Research 2020 - 2023 Tomáš Mészáros @tamasmeszaros, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, David Kocík @kocikdav, Filip Sykala @Jony01, Lukáš Matěna @lukasmatena -///|/ -///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher -///|/ -#include "ArrangeJob.hpp" - -#include "libslic3r/BuildVolume.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/SLAPrint.hpp" -#include "libslic3r/Geometry/ConvexHull.hpp" - -#include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/GUI_ObjectManipulation.hpp" -#include "slic3r/GUI/NotificationManager.hpp" -#include "slic3r/GUI/format.hpp" - - -#include "libnest2d/common.hpp" - -#include -#include - -namespace Slic3r { namespace GUI { - -// Cache the wti info -class WipeTower: public GLCanvas3D::WipeTowerInfo { - using ArrangePolygon = arrangement::ArrangePolygon; -public: - explicit WipeTower(const GLCanvas3D::WipeTowerInfo &wti) - : GLCanvas3D::WipeTowerInfo(wti) - {} - - explicit WipeTower(GLCanvas3D::WipeTowerInfo &&wti) - : GLCanvas3D::WipeTowerInfo(std::move(wti)) - {} - - void apply_arrange_result(const Vec2d& tr, double rotation) - { - m_pos = unscaled(tr); m_rotation = rotation; - apply_wipe_tower(); - } - - ArrangePolygon get_arrange_polygon() const - { - Polygon ap({ - {scaled(m_bb.min)}, - {scaled(m_bb.max.x()), scaled(m_bb.min.y())}, - {scaled(m_bb.max)}, - {scaled(m_bb.min.x()), scaled(m_bb.max.y())} - }); - - ArrangePolygon ret; - ret.poly.contour = std::move(ap); - ret.translation = scaled(m_pos); - ret.rotation = m_rotation; - ++ret.priority; - - return ret; - } -}; - -static WipeTower get_wipe_tower(const Plater &plater) -{ - return WipeTower{plater.canvas3D()->get_wipe_tower_info()}; -} - -void ArrangeJob::clear_input() -{ - const Model &model = m_plater->model(); - - size_t count = 0, cunprint = 0; // To know how much space to reserve - for (auto obj : model.objects) - for (auto mi : obj->instances) - mi->printable ? count++ : cunprint++; - - m_selected.clear(); - m_unselected.clear(); - m_unprintable.clear(); - m_unarranged.clear(); - m_selected.reserve(count + 1 /* for optional wti */); - m_unselected.reserve(count + 1 /* for optional wti */); - m_unprintable.reserve(cunprint /* for optional wti */); -} - -void ArrangeJob::prepare_all() { - clear_input(); - - for (ModelObject *obj: m_plater->model().objects) - for (ModelInstance *mi : obj->instances) { - ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable; - cont.emplace_back(get_arrange_poly_(mi)); - } - - if (auto wti = get_wipe_tower_arrangepoly(*m_plater)) - m_selected.emplace_back(std::move(*wti)); -} - -void ArrangeJob::prepare_selected() { - clear_input(); - - Model &model = m_plater->model(); - - std::vector - obj_sel(model.objects.size(), nullptr); - - for (auto &s : m_plater->get_selection().get_content()) - if (s.first < int(obj_sel.size())) - obj_sel[size_t(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[size_t(inst_id)] = true; - - for (size_t i = 0; i < inst_sel.size(); ++i) { - ModelInstance * mi = mo->instances[i]; - ArrangePolygon &&ap = get_arrange_poly_(mi); - - ArrangePolygons &cont = mo->instances[i]->printable ? - (inst_sel[i] ? m_selected : - m_unselected) : - m_unprintable; - - cont.emplace_back(std::move(ap)); - } - } - - if (auto wti = get_wipe_tower(*m_plater)) { - ArrangePolygon &&ap = get_arrange_poly(wti, m_plater); - - auto &cont = m_plater->get_selection().is_wipe_tower() ? m_selected : - m_unselected; - cont.emplace_back(std::move(ap)); - } - - // If the selection was empty arrange everything - if (m_selected.empty()) - m_selected.swap(m_unselected); -} - -static void update_arrangepoly_slaprint(arrangement::ArrangePolygon &ret, - const SLAPrintObject &po, - const ModelInstance &inst) -{ - // The 1.1 multiplier is a safety gap, as the offset might be bigger - // in sharp edges of a polygon, depending on clipper's offset algorithm - coord_t pad_infl = 0; - { - double infl = po.config().pad_enable.getBool() * ( - po.config().pad_brim_size.getFloat() + - po.config().pad_around_object.getBool() * - po.config().pad_object_gap.getFloat() ); - - pad_infl = scaled(1.1 * infl); - } - - auto laststep = po.last_completed_step(); - - if (laststep < slaposCount && laststep > slaposSupportTree) { - auto omesh = po.get_mesh_to_print(); - auto &smesh = po.support_mesh(); - - Transform3f trafo_instance = inst.get_matrix().cast(); - trafo_instance = trafo_instance * po.trafo().cast().inverse(); - - Polygons polys; - polys.reserve(3); - auto zlvl = -po.get_elevation(); - - if (omesh) { - polys.emplace_back(its_convex_hull_2d_above(*omesh, trafo_instance, zlvl)); - } - - polys.emplace_back(its_convex_hull_2d_above(smesh.its, trafo_instance, zlvl)); - ret.poly.contour = Geometry::convex_hull(polys); - ret.poly.holes = {}; - } - - ret.inflation = pad_infl; -} - -static coord_t brim_offset(const PrintObject &po, const ModelInstance &inst) -{ - const BrimType brim_type = po.config().brim_type.value; - const float brim_separation = po.config().brim_separation.getFloat(); - const float brim_width = po.config().brim_width.getFloat(); - const bool has_outer_brim = brim_type == BrimType::btOuterOnly || - brim_type == BrimType::btOuterAndInner; - - // How wide is the brim? (in scaled units) - return has_outer_brim ? scaled(brim_width + brim_separation) : 0; -} - -arrangement::ArrangePolygon ArrangeJob::get_arrange_poly_(ModelInstance *mi) -{ - arrangement::ArrangePolygon ap = get_arrange_poly(mi, m_plater); - - auto setter = ap.setter; - ap.setter = [this, setter, mi](const arrangement::ArrangePolygon &set_ap) { - setter(set_ap); - if (!set_ap.is_arranged()) - m_unarranged.emplace_back(mi); - }; - - return ap; -} - -coord_t get_skirt_offset(const Plater* plater) { - float skirt_inset = 0.f; - // Try to subtract the skirt from the bed shape so we don't arrange outside of it. - if (plater->printer_technology() == ptFFF && plater->fff_print().has_skirt()) { - const auto& print = plater->fff_print(); - if (!print.objects().empty()) { - skirt_inset = print.config().skirts.value * print.skirt_flow().width() + - print.config().skirt_distance.value; - } - } - - return scaled(skirt_inset); -} - -void ArrangeJob::prepare() -{ - m_selection_only ? prepare_selected() : - prepare_all(); - - coord_t min_offset = 0; - for (auto &ap : m_selected) { - min_offset = std::max(ap.inflation, min_offset); - } - - if (m_plater->printer_technology() == ptSLA) { - // Apply the max offset for all the objects - for (auto &ap : m_selected) { - ap.inflation = min_offset; - } - } else { // it's fff, brims only need to be minded from bed edges - for (auto &ap : m_selected) { - ap.inflation = 0; - } - m_min_bed_inset = min_offset; - } - - double stride = bed_stride(m_plater); - get_bed_shape(*m_plater->config(), m_bed); - assign_logical_beds(m_unselected, m_bed, stride); -} - -void ArrangeJob::process(Ctl &ctl) -{ - static const auto arrangestr = _u8L("Arranging"); - - arrangement::ArrangeParams params; - ctl.call_on_main_thread([this, ¶ms]{ - prepare(); - params = get_arrange_params(m_plater); - coord_t min_inset = get_skirt_offset(m_plater) + m_min_bed_inset; - params.min_bed_distance = std::max(params.min_bed_distance, min_inset); - }).wait(); - - auto count = unsigned(m_selected.size() + m_unprintable.size()); - - if (count == 0) // Should be taken care of by plater, but doesn't hurt - return; - - ctl.update_status(0, arrangestr); - - params.stopcondition = [&ctl]() { return ctl.was_canceled(); }; - - params.progressind = [this, count, &ctl](unsigned st) { - st += m_unprintable.size(); - if (st > 0) ctl.update_status(int(count - st) * 100 / status_range(), arrangestr); - }; - - ctl.update_status(0, arrangestr); - - arrangement::arrange(m_selected, m_unselected, m_bed, params); - - params.progressind = [this, count, &ctl](unsigned st) { - if (st > 0) ctl.update_status(int(count - st) * 100 / status_range(), arrangestr); - }; - - arrangement::arrange(m_unprintable, {}, m_bed, params); - - // finalize just here. - ctl.update_status(int(count) * 100 / status_range(), ctl.was_canceled() ? - _u8L("Arranging canceled.") : - _u8L("Arranging done.")); -} - -ArrangeJob::ArrangeJob(Mode mode) - : m_plater{wxGetApp().plater()}, - m_selection_only{mode == Mode::SelectionOnly} -{} - -static std::string concat_strings(const std::set &strings, - const std::string &delim = "\n") -{ - return std::accumulate( - strings.begin(), strings.end(), std::string(""), - [delim](const std::string &s, const std::string &name) { - return s + name + delim; - }); -} - -void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) { - try { - if (eptr) - std::rethrow_exception(eptr); - } catch (libnest2d::GeometryException &) { - show_error(m_plater, _(L("Could not arrange model objects! " - "Some geometries may be invalid."))); - eptr = nullptr; - } catch(...) { - eptr = std::current_exception(); - } - - if (canceled || eptr) - return; - - // Unprintable items go to the last virtual bed - int beds = 0; - - // Apply the arrange result to all selected objects - for (ArrangePolygon &ap : m_selected) { - beds = std::max(ap.bed_idx, beds); - ap.apply(); - } - - // Get the virtual beds from the unselected items - for (ArrangePolygon &ap : m_unselected) - beds = std::max(ap.bed_idx, beds); - - // Move the unprintable items to the last virtual bed. - for (ArrangePolygon &ap : m_unprintable) { - if (ap.bed_idx >= 0) - ap.bed_idx += beds + 1; - - ap.apply(); - } - - m_plater->update(static_cast( - Plater::UpdateParams::FORCE_FULL_SCREEN_REFRESH)); - - wxGetApp().obj_manipul()->set_dirty(); - - if (!m_unarranged.empty()) { - std::set names; - for (ModelInstance *mi : m_unarranged) - names.insert(mi->get_object()->name); - - m_plater->get_notification_manager()->push_notification(GUI::format( - _L("Arrangement ignored the following objects which can't fit into a single bed:\n%s"), - concat_strings(names, "\n"))); - } -} - -std::optional -get_wipe_tower_arrangepoly(const Plater &plater) -{ - if (auto wti = get_wipe_tower(plater)) - return get_arrange_poly(wti, &plater); - - return {}; -} - -double bed_stride(const Plater *plater) { - double bedwidth = plater->build_volume().bounding_volume().size().x(); - return scaled((1. + LOGICAL_BED_GAP) * bedwidth); -} - -template<> -arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst, - const Plater * plater) -{ - auto ap = get_arrange_poly(PtrWrapper{inst}, plater); - - auto obj_id = inst->get_object()->id(); - if (plater->printer_technology() == ptSLA) { - const SLAPrintObject *po = - plater->sla_print().get_print_object_by_model_object_id(obj_id); - - if (po) { - update_arrangepoly_slaprint(ap, *po, *inst); - } - } else { - const PrintObject *po = - plater->fff_print().get_print_object_by_model_object_id(obj_id); - - if (po) { - ap.inflation = brim_offset(*po, *inst); - } - } - - return ap; -} - -arrangement::ArrangeParams get_arrange_params(Plater *p) -{ - const arr2::ArrangeSettingsView *settings = - p->canvas3D()->get_arrange_settings_view(); - - arrangement::ArrangeParams params; - params.allow_rotations = settings->is_rotation_enabled(); - params.min_obj_distance = scaled(settings->get_distance_from_objects()); - params.min_bed_distance = scaled(settings->get_distance_from_bed()); - - arrangement::Pivots pivot = arrangement::Pivots::Center; - - int pivot_max = static_cast(arrangement::Pivots::TopRight); - if (settings->get_xl_alignment() < 0) { - pivot = arrangement::Pivots::Center; - } else if (settings->get_xl_alignment() == arr2::ArrangeSettingsView::xlpRandom) { - // means it should be random - std::random_device rd{}; - std::mt19937 rng(rd()); - std::uniform_int_distribution dist(0, pivot_max); - pivot = static_cast(dist(rng)); - } else { - pivot = static_cast(settings->get_xl_alignment()); - } - - params.alignment = pivot; - - return params; -} - -void assign_logical_beds(std::vector &items, - const arrangement::ArrangeBed &bed, - double stride) -{ - // The strides have to be removed from the fixed items. For the - // arrangeable (selected) items bed_idx is ignored and the - // translation is irrelevant. - coord_t bedx = bounding_box(bed).min.x(); - for (auto &itm : items) { - auto bedidx = std::max(arrangement::UNARRANGED, - static_cast(std::floor( - (get_extents(itm.transformed_poly()).min.x() - bedx) / - stride))); - - itm.bed_idx = bedidx; - - if (bedidx >= 0) - itm.translation.x() -= bedidx * stride; - } -} - -}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp deleted file mode 100644 index ca9e5a0c9e..0000000000 --- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp +++ /dev/null @@ -1,122 +0,0 @@ -///|/ Copyright (c) Prusa Research 2020 - 2023 Tomáš Mészáros @tamasmeszaros, David Kocík @kocikdav -///|/ -///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher -///|/ -#ifndef ARRANGEJOB_HPP -#define ARRANGEJOB_HPP - -#include - -#include "Job.hpp" -#include "libslic3r/Arrange.hpp" - -namespace Slic3r { - -class ModelInstance; - -namespace GUI { - -class Plater; - -class ArrangeJob : public Job -{ - using ArrangePolygon = arrangement::ArrangePolygon; - using ArrangePolygons = arrangement::ArrangePolygons; - - ArrangePolygons m_selected, m_unselected, m_unprintable; - std::vector m_unarranged; - arrangement::ArrangeBed m_bed; - coord_t m_min_bed_inset = 0.; - - Plater *m_plater; - bool m_selection_only = false; - - // clear m_selected and m_unselected, reserve space for next usage - void clear_input(); - - // Prepare all objects on the bed regardless of the selection - void prepare_all(); - - // Prepare the selected and unselected items separately. If nothing is - // selected, behaves as if everything would be selected. - void prepare_selected(); - - ArrangePolygon get_arrange_poly_(ModelInstance *mi); - -public: - - enum Mode { Full, SelectionOnly }; - - void prepare(); - - void process(Ctl &ctl) override; - - ArrangeJob(Mode mode = Full); - - int status_range() const - { - return int(m_selected.size() + m_unprintable.size()); - } - - void finalize(bool canceled, std::exception_ptr &e) override; -}; - -std::optional get_wipe_tower_arrangepoly(const Plater &); - -// 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.; - -// Stride between logical beds -double bed_stride(const Plater *plater); - -template struct PtrWrapper -{ - T *ptr; - - explicit PtrWrapper(T *p) : ptr{p} {} - - arrangement::ArrangePolygon get_arrange_polygon() const - { - return ptr->get_arrange_polygon(); - } - - void apply_arrange_result(const Vec2d &t, double rot) - { - ptr->apply_arrange_result(t, rot); - } -}; - -// Set up arrange polygon for a ModelInstance and Wipe tower -template -arrangement::ArrangePolygon get_arrange_poly(T obj, const Plater *plater) -{ - using ArrangePolygon = arrangement::ArrangePolygon; - - ArrangePolygon ap = obj.get_arrange_polygon(); - ap.setter = [obj, plater](const ArrangePolygon &p) { - if (p.is_arranged()) { - Vec2d t = p.translation.cast(); - t.x() += p.bed_idx * bed_stride(plater); - T{obj}.apply_arrange_result(t, p.rotation); - } - }; - - return ap; -} - -template<> -arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst, - const Plater * plater); - -arrangement::ArrangeParams get_arrange_params(Plater *p); - -coord_t get_skirt_offset(const Plater* plater); - -void assign_logical_beds(std::vector &items, - const arrangement::ArrangeBed &bed, - double stride); - -}} // namespace Slic3r::GUI - -#endif // ARRANGEJOB_HPP diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp deleted file mode 100644 index d696c7339f..0000000000 --- a/src/slic3r/GUI/Jobs/FillBedJob.cpp +++ /dev/null @@ -1,207 +0,0 @@ -///|/ Copyright (c) Prusa Research 2020 - 2023 Tomáš Mészáros @tamasmeszaros -///|/ -///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher -///|/ -#include "FillBedJob.hpp" - -#include "libslic3r/Model.hpp" -#include "libslic3r/ClipperUtils.hpp" - -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/GUI_ObjectList.hpp" - -#include - -namespace Slic3r { -namespace GUI { - -void FillBedJob::prepare() -{ - m_selected.clear(); - m_unselected.clear(); - m_min_bed_inset = 0.; - - m_object_idx = m_plater->get_selected_object_idx(); - if (m_object_idx == -1) - return; - - ModelObject *model_object = m_plater->model().objects[m_object_idx]; - if (model_object->instances.empty()) - return; - - m_selected.reserve(model_object->instances.size()); - for (ModelInstance *inst : model_object->instances) - if (inst->printable) { - ArrangePolygon ap = get_arrange_poly(inst, m_plater); - // Existing objects need to be included in the result. Only - // the needed amount of object will be added, no more. - ++ap.priority; - m_selected.emplace_back(ap); - } - - if (m_selected.empty()) - return; - - Points bedpts = get_bed_shape(*m_plater->config()); - - auto &objects = m_plater->model().objects; - BoundingBox bedbb = get_extents(bedpts); - - for (size_t idx = 0; idx < objects.size(); ++idx) - if (int(idx) != m_object_idx) - for (ModelInstance *mi : objects[idx]->instances) { - ArrangePolygon ap = get_arrange_poly(PtrWrapper{mi}, m_plater); - auto ap_bb = ap.transformed_poly().contour.bounding_box(); - - if (ap.bed_idx == 0 && !bedbb.contains(ap_bb)) - ap.bed_idx = arrangement::UNARRANGED; - - m_unselected.emplace_back(ap); - } - - if (auto wt = get_wipe_tower_arrangepoly(*m_plater)) - m_unselected.emplace_back(std::move(*wt)); - - double sc = scaled(1.) * scaled(1.); - - ExPolygon poly = m_selected.front().poly; - double poly_area = poly.area() / sc; - double unsel_area = std::accumulate(m_unselected.begin(), - m_unselected.end(), 0., - [](double s, const auto &ap) { - return s + (ap.bed_idx == 0) * ap.poly.area(); - }) / sc; - - double fixed_area = unsel_area + m_selected.size() * poly_area; - double bed_area = Polygon{bedpts}.area() / sc; - - // This is the maximum number of items, the real number will always be close but less. - int needed_items = (bed_area - fixed_area) / poly_area; - - int sel_id = m_plater->get_selection().get_instance_idx(); - // if the selection is not a single instance, choose the first as template - sel_id = std::max(sel_id, 0); - ModelInstance *mi = model_object->instances[sel_id]; - ArrangePolygon template_ap = get_arrange_poly(PtrWrapper{mi}, m_plater); - - for (int i = 0; i < needed_items; ++i) { - ArrangePolygon ap = template_ap; - ap.bed_idx = arrangement::UNARRANGED; - auto m = mi->get_transformation(); - ap.setter = [this, m](const ArrangePolygon &p) { - ModelObject *mo = m_plater->model().objects[m_object_idx]; - ModelInstance *inst = mo->add_instance(m); - inst->apply_arrange_result(p.translation.cast(), p.rotation); - }; - m_selected.emplace_back(ap); - } - - m_status_range = m_selected.size(); - - coord_t min_offset = 0; - for (auto &ap : m_selected) { - min_offset = std::max(ap.inflation, min_offset); - } - - if (m_plater->printer_technology() == ptSLA) { - // Apply the max offset for all the objects - for (auto &ap : m_selected) { - ap.inflation = min_offset; - } - } else { // it's fff, brims only need to be minded from bed edges - for (auto &ap : m_selected) { - ap.inflation = 0; - } - m_min_bed_inset = min_offset; - } - - // The strides have to be removed from the fixed items. For the - // arrangeable (selected) items bed_idx is ignored and the - // translation is irrelevant. - double stride = bed_stride(m_plater); - - m_bed = arrangement::to_arrange_bed(bedpts); - assign_logical_beds(m_unselected, m_bed, stride); -} - -void FillBedJob::process(Ctl &ctl) -{ - auto statustxt = _u8L("Filling bed"); - arrangement::ArrangeParams params; - ctl.call_on_main_thread([this, ¶ms] { - prepare(); - params = get_arrange_params(m_plater); - coord_t min_inset = get_skirt_offset(m_plater) + m_min_bed_inset; - params.min_bed_distance = std::max(params.min_bed_distance, min_inset); - }).wait(); - ctl.update_status(0, statustxt); - - if (m_object_idx == -1 || m_selected.empty()) - return; - - bool do_stop = false; - params.stopcondition = [&ctl, &do_stop]() { - return ctl.was_canceled() || do_stop; - }; - - params.progressind = [this, &ctl, &statustxt](unsigned st) { - if (st > 0) - ctl.update_status(int(m_status_range - st) * 100 / status_range(), statustxt); - }; - - params.on_packed = [&do_stop] (const ArrangePolygon &ap) { - do_stop = ap.bed_idx > 0 && ap.priority == 0; - }; - - arrangement::arrange(m_selected, m_unselected, m_bed, params); - - // finalize just here. - ctl.update_status(100, ctl.was_canceled() ? - _u8L("Bed filling canceled.") : - _u8L("Bed filling done.")); -} - -FillBedJob::FillBedJob() : m_plater{wxGetApp().plater()} {} - -void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr) -{ - // Ignore the arrange result if aborted. - if (canceled || eptr) - return; - - if (m_object_idx == -1) - return; - - ModelObject *model_object = m_plater->model().objects[m_object_idx]; - if (model_object->instances.empty()) - return; - - size_t inst_cnt = model_object->instances.size(); - - int added_cnt = std::accumulate(m_selected.begin(), m_selected.end(), 0, [](int s, auto &ap) { - return s + int(ap.priority == 0 && ap.bed_idx == 0); - }); - - if (added_cnt > 0) { - for (ArrangePolygon &ap : m_selected) { - if (ap.bed_idx != arrangement::UNARRANGED && (ap.priority != 0 || ap.bed_idx == 0)) - ap.apply(); - } - - model_object->ensure_on_bed(); - - m_plater->update(static_cast( - Plater::UpdateParams::FORCE_FULL_SCREEN_REFRESH)); - - // FIXME: somebody explain why this is needed for increase_object_instances - if (inst_cnt == 1) - added_cnt++; - - m_plater->sidebar() - .obj_list()->increase_object_instances(m_object_idx, size_t(added_cnt)); - } -} - -}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/FillBedJob.hpp b/src/slic3r/GUI/Jobs/FillBedJob.hpp deleted file mode 100644 index faf3296f2e..0000000000 --- a/src/slic3r/GUI/Jobs/FillBedJob.hpp +++ /dev/null @@ -1,46 +0,0 @@ -///|/ Copyright (c) Prusa Research 2020 - 2023 Tomáš Mészáros @tamasmeszaros, David Kocík @kocikdav -///|/ -///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher -///|/ -#ifndef FILLBEDJOB_HPP -#define FILLBEDJOB_HPP - -#include "ArrangeJob.hpp" - -namespace Slic3r { namespace GUI { - -class Plater; - -class FillBedJob : public Job -{ - int m_object_idx = -1; - - using ArrangePolygon = arrangement::ArrangePolygon; - using ArrangePolygons = arrangement::ArrangePolygons; - - ArrangePolygons m_selected; - ArrangePolygons m_unselected; - coord_t m_min_bed_inset = 0.; - - arrangement::ArrangeBed m_bed; - - int m_status_range = 0; - Plater *m_plater; - -public: - void prepare(); - void process(Ctl &ctl) override; - - FillBedJob(); - - int status_range() const /*override*/ - { - return m_status_range; - } - - void finalize(bool canceled, std::exception_ptr &e) override; -}; - -}} // namespace Slic3r::GUI - -#endif // FILLBEDJOB_HPP