From 706359a6b63f290fecf12fa4dee46367d1487b57 Mon Sep 17 00:00:00 2001 From: David Grundberg <75159519+individ-divided@users.noreply.github.com> Date: Sun, 30 Oct 2022 05:28:36 +0100 Subject: [PATCH 1/3] libnest2d changes --- src/libnest2d/include/libnest2d/nester.hpp | 134 +++++++++++++++++- .../include/libnest2d/placers/nfpplacer.hpp | 68 ++++++--- 2 files changed, 178 insertions(+), 24 deletions(-) diff --git a/src/libnest2d/include/libnest2d/nester.hpp b/src/libnest2d/include/libnest2d/nester.hpp index 52c738a4c18..26372dcd99f 100644 --- a/src/libnest2d/include/libnest2d/nester.hpp +++ b/src/libnest2d/include/libnest2d/nester.hpp @@ -32,9 +32,14 @@ class _Item { using VertexConstIterator = typename TContour::const_iterator; - // The original shape that gets encapsulated. + // An original shape that gets encapsulated. + // To be fitted against other _Item's sh_ RawShape sh_; + // An original shape that gets encapsulated. + // To be fitted against the bin edges + RawShape bin_fit_shape_; + // Transformation data Vertex translation_{0, 0}; Radians rotation_{0.0}; @@ -48,6 +53,8 @@ class _Item { // For caching the calculations as they can get pretty expensive. mutable RawShape tr_cache_; mutable bool tr_cache_valid_ = false; + mutable RawShape bfs_cache_; + mutable bool bfs_cache_valid_ = false; mutable double area_cache_ = 0; mutable bool area_cache_valid_ = false; mutable RawShape inflate_cache_; @@ -67,7 +74,8 @@ class _Item { Box bb; bool valid; BBCache(): valid(false) {} } bb_cache_; - + mutable struct BBCache bfs_bb_cache_; + int binid_{BIN_ID_UNSET}, priority_{0}; bool fixed_{false}; @@ -288,14 +296,41 @@ class _Item { { rotation(rotation() + rads); } - + inline void inflation(Coord distance) BP2D_NOEXCEPT { inflation_ = distance; has_inflation_ = true; invalidateCache(); } - + + /** + * @brief Setup the shape that is packed against the bed edges. + * + * If this shape is set, the item will be packed against the bed + * borders with this shape. Packing with other items is still + * done with the shape set in the constructor. + * + * The shape contour will be copied into the _Item object. + * + * Note: This shape, if set, is not inflated with the ::inflation + * property. + * + * @param contour Item-to-bin shape + */ + inline void binFitShape(const TContour& contour) BP2D_NOEXCEPT + { + const THolesContainer holes = {}; + bin_fit_shape_ = sl::create(contour, holes); + invalidateCache(); + } + + inline void binFitShape(RawShape&& shape) BP2D_NOEXCEPT + { + bin_fit_shape_ = std::move(shape); + invalidateCache(); + } + inline Coord inflation() const BP2D_NOEXCEPT { return inflation_; } @@ -321,6 +356,8 @@ class _Item { rotation_ = rot; has_rotation_ = true; tr_cache_valid_ = false; rmt_valid_ = false; lmb_valid_ = false; bb_cache_.valid = false; + bfs_bb_cache_.valid = false; + bfs_cache_valid_ = false; } } @@ -328,10 +365,15 @@ class _Item { { if(translation_ != tr) { translation_ = tr; has_translation_ = true; tr_cache_valid_ = false; - //bb_cache_.valid = false; + bfs_cache_valid_ = false; } } + /** + * @brief The inflated, rotated and translated item shape. + * + * Used to pack items against other items. + */ inline const RawShape& transformedShape() const { if(tr_cache_valid_) return tr_cache_; @@ -345,6 +387,34 @@ class _Item { return tr_cache_; } + /** + * @brief Returns a rotated and translated shape for fitting + * against the bin borders. + * + * This shape is used to check if the item fits inside the borders + * of the bin. + * + * If a \link binFitShape bin fit shape is set\endlink, this + * returns that shape rotated and translated. + * + * If a bin fit shape is not set, this returns the inflated + * item shape, rotated and translated. It's the same result as + * transformedShape(), for backwards compability. + */ + inline const RawShape& transformedBinFitShape() const + { + if(!bin_fit_shape_.is_valid()) + return transformedShape(); + if(bfs_cache_valid_) return bfs_cache_; + + RawShape cpy = bin_fit_shape_; + if(has_rotation_) sl::rotate(cpy, rotation_); + if(has_translation_) sl::translate(cpy, translation_); + bfs_cache_ = cpy; bfs_cache_valid_ = true; + + return bfs_cache_; + } + inline operator RawShape() const { return transformedShape(); @@ -355,6 +425,24 @@ class _Item { return sh_; } + /** + * @brief Returns the bin fit shape. + * + * This shape is used for the bin boundary checking. + * + * If a \link binFitShape bin fit shape was setup\endlink, this + * returns that shape. No rotation or translation applied. + * + * If a bin fit shape was not set up, this returns the inflated + * item shape. It's not rotated or translated. + */ + inline const RawShape& untransformedBinFitShape() const BP2D_NOEXCEPT + { + if(bin_fit_shape_.is_valid()) + return bin_fit_shape_; + return infaltedShape(); + } + inline void resetTransformation() BP2D_NOEXCEPT { has_translation_ = false; has_rotation_ = false; has_inflation_ = false; @@ -378,6 +466,40 @@ class _Item { return {bb.minCorner() + tr, bb.maxCorner() + tr }; } + /** + * @brief Returns the bounding box for fitting against the bin + * borders. + * + * This bounding box is used to check if the item fits inside the + * borders of the bin. + * + * If a \link binFitShape bin fit shape was setup\endlink, this + * returns the bounding box of that shape rotated and translated. + * + * If a bin fit shape was not set up, this returns the bounding + * box of the inflated item shape, rotated and translated. It's + * the same result as boundingBox(), for backwards + * compability. + */ + inline Box binFitShapeBoundingBox() const { + if(!bin_fit_shape_.is_valid()) + return boundingBox(); + if(!bfs_bb_cache_.valid) { + if(!has_rotation_) + bfs_bb_cache_.bb = sl::boundingBox(bin_fit_shape_); + else { + // TODO make sure this works + auto rotsh = bin_fit_shape_; + sl::rotate(rotsh, rotation_); + bfs_bb_cache_.bb = sl::boundingBox(rotsh); + } + bfs_bb_cache_.valid = true; + } + + auto &bfs_bb = bfs_bb_cache_.bb; auto &tr = translation_; + return {bfs_bb.minCorner() + tr, bfs_bb.maxCorner() + tr }; + } + inline Vertex referenceVertex() const { return rightmostTopVertex(); } @@ -431,10 +553,12 @@ class _Item { inline void invalidateCache() const BP2D_NOEXCEPT { tr_cache_valid_ = false; + bfs_cache_valid_ = false; lmb_valid_ = false; rmt_valid_ = false; area_cache_valid_ = false; inflate_cache_valid_ = false; bb_cache_.valid = false; + bfs_bb_cache_.valid = false; convexity_ = Convexity::UNCHECKED; } diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 00f6a999fb9..1346afea7ba 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -90,11 +90,19 @@ struct NfpPConfig { bool parallel = true; /** - * @brief before_packing Callback that is called just before a search for - * a new item's position is started. You can use this to create various - * cache structures and update them between subsequent packings. + * @brief Callback that is called just before a search for a new + * item's position is started. * - * \param merged pile A polygon that is the union of all items in the bin. + * You can use this to create various cache structures and update + * them between subsequent packings. + * + * \param merged_pile A polygon that is the union of all items in the bin. + * + * \param merged_bin_fit_pile Similar to merged_pile, this is a + * union of all the items' \link _Item::transformedBinFitShape bin + * fit shapes\endlink. Typically used for checking against the + * border of the bin. + * \see _Item::transformedBinFitShape() * * \param pile The items parameter is a container with all the placed * polygons excluding the current candidate. You can for instance check the @@ -112,7 +120,26 @@ struct NfpPConfig { * into an L shape. This parameter can be used to make these kind of * decisions (for you or a more intelligent AI). */ - std::function&, // merged pile + std::function&, // merged pile, item to item shapes + const nfp::Shapes&, // merged pile, bin fit shapes + const ItemGroup&, // packed items + const ItemGroup& // remaining items + )> before_packing_ex; + + /** + * @brief This is a simpler version of + * #before_packing_ex without the bin_fit_shape_merged_pile + * parameter. + * + * \param merged_pile A polygon that is the union of all items in the bin. + * + * \param pile The items parameter is a container with all the placed + * polygons excluding the current candidate. + * + * \param remaining A container with the remaining items waiting to be + * placed. + */ + std::function&, // merged pile, item to item shapes const ItemGroup&, // packed items const ItemGroup& // remaining items )> before_packing; @@ -440,6 +467,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer; using OptResults = std::vector; @@ -890,6 +919,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer m; m.reserve(items_.size()); - for(Item& item : items_) m.emplace_back(item.transformedShape()); + for(Item& item : items_) m.emplace_back(item.transformedBinFitShape()); auto c = boundingCircle(sl::convexHull(m)); @@ -920,9 +950,9 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer Date: Sun, 16 Oct 2022 14:47:27 +0200 Subject: [PATCH 2/3] Arrange according to the brim and skirt settings (supports brim per object) Make the arrange function place objects so that the skirt and all the brims fit inside the bed. --- src/libslic3r/Arrange.cpp | 37 +++++++++++-------- src/libslic3r/Arrange.hpp | 15 ++++++-- src/slic3r/GUI/Jobs/ArrangeJob.cpp | 57 ++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index bf2a219d0de..6206d1c75dd 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -151,6 +151,8 @@ class AutoArranger { 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. + MultiPolygon m_merged_bed_fit_pile; // The already merged pile (vector of items), "first layer" + Box m_bed_fit_pilebb; // The bounding box of the merged pile, "first layer" ItemGroup m_remaining; // Remaining items ItemGroup m_items; // allready packed items size_t m_item_count = 0; // Number of all items to be packed @@ -299,8 +301,11 @@ class AutoArranger { break; } } - - return std::make_tuple(score, fullbb); + + // Calculate the full bounding box of the pile with the candidate item + auto bed_fit_bb = sl::boundingBox(m_bed_fit_pilebb, item.binFitShapeBoundingBox()); + + return std::make_tuple(score, bed_fit_bb); } std::function get_objfn(); @@ -319,16 +324,19 @@ class AutoArranger { // 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 + m_pconf.before_packing_ex = + [this](const MultiPolygon& merged_pile, // merged pile of poly-to-poly shapes + const MultiPolygon& merged_bin_fit_pile, // merged pile of bin fit shapes const ItemGroup& items, // packed items const ItemGroup& remaining) // future items to be packed { m_items = items; m_merged_pile = merged_pile; + m_merged_bed_fit_pile = merged_bin_fit_pile; m_remaining = remaining; m_pilebb = sl::boundingBox(merged_pile); + m_bed_fit_pilebb = sl::boundingBox(merged_bin_fit_pile); m_rtree.clear(); m_smallsrtree.clear(); @@ -435,8 +443,8 @@ template<> std::function AutoArranger::get_objfn() }; if(isBig(item)) { - auto mp = m_merged_pile; - mp.push_back(item.transformedShape()); + auto mp = m_merged_bed_fit_pile; + mp.push_back(item.transformedBinFitShape()); auto chull = sl::convexHull(mp); double miss = Placer::overfit(chull, m_bin); if(miss < 0) miss = 0; @@ -462,7 +470,7 @@ template void remove_large_items(std::vector &items, Bin &&bin) { auto it = items.begin(); while (it != items.end()) - sl::isInside(it->transformedShape(), bin) ? + sl::isInside(it->transformedBinFitShape(), bin) ? ++it : it = items.erase(it); } @@ -491,18 +499,16 @@ void _arrange( coord_t md = params.min_obj_distance; md = md / 2; - 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}; + AutoArranger arranger{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); + remove_large_items(excludes, bin); // If there is something on the plate if (!excludes.empty()) arranger.preload(excludes); @@ -517,13 +523,13 @@ void _arrange( // 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())); + itm.rotation(min_area_boundingbox_rotation(itm.untransformedBinFitShape())); // 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(); + auto bb = itm.binFitShapeBoundingBox(); if (bb.width() >= bin.width() || bb.height() >= bin.height()) - itm.rotate(fit_into_box_rotation(itm.transformedShape(), bin)); + itm.rotate(fit_into_box_rotation(itm.transformedBinFitShape(), bin)); } } } @@ -581,12 +587,15 @@ static void process_arrangeable(const ArrangePolygon &arrpoly, const Vec2crd &offs = arrpoly.translation; double rotation = arrpoly.rotation; + Polygon first_layer_poly = arrpoly.first_layer_poly.contour; + // This fixes: // https://github.com/prusa3d/PrusaSlicer/issues/2209 if (p.points.size() < 3) return; outp.emplace_back(std::move(p)); + outp.back().binFitShape(std::move(first_layer_poly)); outp.back().rotation(rotation); outp.back().translation({offs.x(), offs.y()}); outp.back().binId(arrpoly.bed_idx); diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 0ff87c88d53..f5bbe84bc5b 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -42,12 +42,21 @@ static const constexpr int UNARRANGED = -1; /// 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 +struct ArrangePolygon { + /// The 2D silhouette that will be packed against other + /// ArrangePolygon.poly's. Must be a convex hull. + ExPolygon poly; + + /// The 2D silhouette that will be packed against the bed + /// edges. Must be a convex hull. + ExPolygon first_layer_poly; 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... + + /// How early should we try to place this ArrangePolygon? Higher + /// number is earlier. Used to make sure the wipe tower is placed + /// on the first bed. int priority{0}; // If empty, any rotation is allowed (currently unsupported) diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 2771f9d2715..12c5be8be19 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -1,8 +1,10 @@ #include "ArrangeJob.hpp" +#include "libslic3r/ClipperUtils.hpp" #include "libslic3r/BuildVolume.hpp" #include "libslic3r/MTUtils.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/Print.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" @@ -154,6 +156,61 @@ arrangement::ArrangePolygon ArrangeJob::get_arrange_poly_(ModelInstance *mi) m_unarranged.emplace_back(mi); }; + const bool is_fff = m_plater->printer_technology() == ptFFF; + if (is_fff) { + const Print &print = m_plater->fff_print(); + const PrintConfig &print_config = print.config(); + const DynamicPrintConfig & object_config = mi->get_object()->config.get(); + const DynamicPrintConfig * plater_config = m_plater->config(); + DynamicPrintConfig config = *plater_config; + config.apply_only(object_config, {"brim_type", "brim_separation", "brim_width"}); + assert(config.has("brim_type")); + assert(config.has("brim_separation")); + assert(config.has("brim_width")); + const BrimType brim_type = config.opt_enum("brim_type"); + const float brim_separation = config.opt_float("brim_separation"); + const float brim_width = config.opt_float("brim_width"); + const bool draft_shield = print_config.draft_shield != dsDisabled; + const bool has_outer_brim = brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner; + + // How wide is the brim? (in scaled units) + const coord_t brim_margin = scaled(brim_width + brim_separation); + + // How wide is the skirt? (in scaled units) + coord_t skirt_margin = 0; + + if (print.has_skirt()) + { + float skirt_margin_mm = 0.f; + const Flow skirt_flow = print.skirt_flow(); + const float spacing = skirt_flow.spacing(); + size_t n_skirts = print_config.skirts.value; + skirt_margin_mm += print_config.skirt_distance.value; + skirt_margin_mm += (n_skirts + 0.5f) * spacing; + skirt_margin = scaled(skirt_margin_mm); + } + + coord_t expansion = 0; + if (has_outer_brim) + { + if (draft_shield) + expansion = std::max(brim_margin, skirt_margin); + else + expansion = brim_margin + skirt_margin; + } + else + expansion = skirt_margin; + + ap.first_layer_poly = ap.poly; + + // This is an overestimation of the distance needed between + // object and bed edge. To get tighter results, one would need + // to look at the first layers of the object. + ExPolygons result = offset_ex(ap.first_layer_poly, expansion, Slic3r::ClipperLib::jtSquare); + if (!result.empty()) + ap.first_layer_poly = ExPolygon(result.front()); + + } return ap; } From cb78538825efeb4d98fb017a5f02afa3acf0dd76 Mon Sep 17 00:00:00 2001 From: David Grundberg <75159519+individ-divided@users.noreply.github.com> Date: Sat, 12 Nov 2022 14:34:29 +0100 Subject: [PATCH 3/3] Arrange: Do not segfault if the print has never been sliced. I can't figure out how to make fff_print valid here so I decided to just reimplement the flow calculation. --- src/slic3r/GUI/Jobs/ArrangeJob.cpp | 46 +++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 12c5be8be19..f5e508544b7 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -145,6 +145,34 @@ void ArrangeJob::prepare_selected() { for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride; } +static Flow skirt_flow_approximation(DynamicPrintConfig const& config) +{ + assert(config.has("first_layer_extrusion_width")); + assert(config.has("perimeter_extrusion_width")); + assert(config.has("extrusion_width")); + assert(config.has("first_layer_height")); + assert(config.has("support_material_extruder")); + assert(config.has("nozzle_diameter")); + ConfigOptionFloatOrPercent width = *config.option("first_layer_extrusion_width"); + if (width.value == 0) + width = *config.option("perimeter_extrusion_width"); + if (width.value == 0) + width = *config.option("extrusion_width"); + + ConfigOptionFloatOrPercent first_layer_height = *config.option("first_layer_height"); + assert(! first_layer_height.percent); + + int extruder = config.opt_int("support_material_extruder"); + + ConfigOptionFloats nozzle_diameter = *config.option("nozzle_diameter"); + + return Flow::new_from_config_width( + frPerimeter, + width, + (float)nozzle_diameter.get_at(extruder), + (float)first_layer_height.value); +} + arrangement::ArrangePolygon ArrangeJob::get_arrange_poly_(ModelInstance *mi) { arrangement::ArrangePolygon ap = get_arrange_poly(mi, m_plater); @@ -179,15 +207,19 @@ arrangement::ArrangePolygon ArrangeJob::get_arrange_poly_(ModelInstance *mi) // How wide is the skirt? (in scaled units) coord_t skirt_margin = 0; + config.apply_only(print_config, {"first_layer_extrusion_width", "perimeter_extrusion_width", "extrusion_width"}); + config.apply_only(object_config, {"first_layer_extrusion_width", "perimeter_extrusion_width", "extrusion_width"}); + const Flow skirt_flow = skirt_flow_approximation(config); + if (print.has_skirt()) { - float skirt_margin_mm = 0.f; - const Flow skirt_flow = print.skirt_flow(); - const float spacing = skirt_flow.spacing(); - size_t n_skirts = print_config.skirts.value; - skirt_margin_mm += print_config.skirt_distance.value; - skirt_margin_mm += (n_skirts + 0.5f) * spacing; - skirt_margin = scaled(skirt_margin_mm); + int skirts = print_config.skirts.value; + if (skirts == 0 && print.has_infinite_skirt()) + skirts = 1; + skirt_margin = scaled( + print_config.skirt_distance.value + + (skirts + 0.5f) * skirt_flow.spacing() + ); } coord_t expansion = 0;