From 6b1c9119bed3e88534beeebbf30870b02fe0d157 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 15 Feb 2023 16:27:03 +0100 Subject: [PATCH 1/4] Add code to extract sla supports outline for arrangement Add functionality to libnest to support on_packed handler for all items. Use it to swap arrange polygon from envelope to core poly after packing was succesful. Solving brim issue by offset adjustments of arrange polygons Only in sla yet --- src/libnest2d/include/libnest2d/nester.hpp | 18 +++++ .../include/libnest2d/placers/nfpplacer.hpp | 1 + src/libslic3r/Arrange.cpp | 4 ++ src/libslic3r/Arrange.hpp | 2 +- src/slic3r/GUI/Jobs/ArrangeJob.cpp | 68 ++++++++++++++++++- src/slic3r/GUI/Jobs/ArrangeJob.hpp | 2 + 6 files changed, 93 insertions(+), 2 deletions(-) diff --git a/src/libnest2d/include/libnest2d/nester.hpp b/src/libnest2d/include/libnest2d/nester.hpp index 52c738a4c1..78da28759c 100644 --- a/src/libnest2d/include/libnest2d/nester.hpp +++ b/src/libnest2d/include/libnest2d/nester.hpp @@ -70,6 +70,7 @@ class _Item { int binid_{BIN_ID_UNSET}, priority_{0}; bool fixed_{false}; + std::function on_packed_; public: @@ -205,6 +206,23 @@ public: sl::vertex(sh_, idx) = v; } + void setShape(RawShape rsh) + { + sh_ = std::move(rsh); + invalidateCache(); + } + + void setOnPackedFn(std::function onpackedfn) + { + on_packed_ = onpackedfn; + } + + void onPacked() + { + if (on_packed_) + on_packed_(*this); + } + /** * @brief Calculate the shape area. * diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index a17d549822..0ef0929e16 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -901,6 +901,7 @@ public: if(can_pack) { ret = PackResult(item); + item.onPacked(); merged_pile_ = nfp::merge(merged_pile_, item.transformedShape()); } else { ret = PackResult(best_overfit); diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 31751ab322..b3293e17f2 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -583,8 +583,12 @@ static void process_arrangeable(const ArrangePolygon &arrpoly, 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) diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 6d4001a50c..4ed00668c9 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -71,7 +71,7 @@ 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 { +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 diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 4595ae2544..72a938ab33 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -2,6 +2,8 @@ #include "libslic3r/BuildVolume.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/SLAPrint.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" @@ -11,6 +13,7 @@ #include "slic3r/GUI/NotificationManager.hpp" #include "slic3r/GUI/format.hpp" + #include "libnest2d/common.hpp" #include @@ -76,6 +79,7 @@ void ArrangeJob::clear_input() m_selected.reserve(count + 1 /* for optional wti */); m_unselected.reserve(count + 1 /* for optional wti */); m_unprintable.reserve(cunprint /* for optional wti */); + m_min_inflation = 0; } void ArrangeJob::prepare_all() { @@ -145,6 +149,50 @@ void ArrangeJob::prepare_selected() { for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride; } +static void update_arrangepoly_slaprint(arrangement::ArrangePolygon &ret, + const SLAPrintObject &po, + const ModelInstance &inst, + coord_t min_infl) +{ + auto laststep = po.last_completed_step(); + + if (laststep < slaposCount && laststep > slaposSupportTree) { + auto omesh = po.get_mesh_to_print(); + auto &smesh = po.support_mesh(); + + Vec3d rotation = inst.get_rotation(); + rotation.z() = 0.; + Transform3f trafo_instance = + Geometry::assemble_transform(inst.get_offset().z() * Vec3d::UnitZ(), + rotation, + inst.get_scaling_factor(), + inst.get_mirror()).cast(); + + trafo_instance = trafo_instance * po.trafo().cast().inverse(); + + auto polys = reserve_vector(3); + auto zlvl = -po.get_elevation(); + + if (omesh) { + polys.emplace_back(its_convex_hull_2d_above(*omesh, trafo_instance, zlvl)); + ret.poly.contour = polys.back(); + ret.poly.holes = {}; + } + + polys.emplace_back(its_convex_hull_2d_above(smesh.its, trafo_instance, zlvl)); + ret.poly.contour = Geometry::convex_hull(polys); + ret.poly.holes = {}; + + // 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 infl = 1.1 * scaled(po.config().pad_brim_size.getFloat() + + po.config().pad_around_object.getBool() * + po.config().pad_object_gap.getFloat()); + + ret.inflation = std::max(infl, min_infl); + } +} + arrangement::ArrangePolygon ArrangeJob::get_arrange_poly_(ModelInstance *mi) { arrangement::ArrangePolygon ap = get_arrange_poly(mi, m_plater); @@ -156,12 +204,28 @@ arrangement::ArrangePolygon ArrangeJob::get_arrange_poly_(ModelInstance *mi) m_unarranged.emplace_back(mi); }; + if (m_plater->printer_technology() == ptSLA) { + auto obj_id = mi->get_object()->id(); + const SLAPrintObject *po = + m_plater->sla_print().get_print_object_by_model_object_id(obj_id); + + if (po) { + update_arrangepoly_slaprint(ap, *po, *mi, m_min_inflation); + m_min_inflation = std::max(m_min_inflation, ap.inflation); + } + } else { + // TODO: get fff supports outline + } + return ap; } void ArrangeJob::prepare() { wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all(); + for (auto &ap : m_selected) { + ap.inflation = m_min_inflation; + } } void ArrangeJob::process(Ctl &ctl) @@ -286,7 +350,9 @@ template<> arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst, const Plater * plater) { - return get_arrange_poly(PtrWrapper{inst}, plater); + auto ap = get_arrange_poly(PtrWrapper{inst}, plater); + + return ap; } arrangement::ArrangeParams get_arrange_params(Plater *p) diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp index 106cc57ddf..4b75868a03 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp @@ -21,6 +21,8 @@ class ArrangeJob : public Job ArrangePolygons m_selected, m_unselected, m_unprintable; std::vector m_unarranged; + coord_t m_min_inflation = 0; + Plater *m_plater; // clear m_selected and m_unselected, reserve space for next usage From 3c5ecd4a8fb473d06fde8555633856239ff81a20 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 16 Feb 2023 16:06:21 +0100 Subject: [PATCH 2/4] FFF arrange minding skirt and brim distances many thanks to @individ-divided and @jschuh --- src/libslic3r/Print.hpp | 5 ++ src/slic3r/GUI/Jobs/ArrangeJob.cpp | 75 +++++++++++++++++++++++------- src/slic3r/GUI/Jobs/ArrangeJob.hpp | 4 +- src/slic3r/GUI/Jobs/FillBedJob.cpp | 22 ++++++++- src/slic3r/GUI/Jobs/FillBedJob.hpp | 1 + 5 files changed, 89 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 9fbbe378a1..f5712c3de6 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -565,6 +565,11 @@ public: SpanOfConstPtrs objects() const { return SpanOfConstPtrs(const_cast(m_objects.data()), m_objects.size()); } PrintObject* get_object(size_t idx) { return const_cast(m_objects[idx]); } const PrintObject* get_object(size_t idx) const { return m_objects[idx]; } + const PrintObject* get_print_object_by_model_object_id(ObjectID object_id) const { + auto it = std::find_if(m_objects.begin(), m_objects.end(), + [object_id](const PrintObject* obj) { return obj->model_object()->id() == object_id; }); + return (it == m_objects.end()) ? nullptr : *it; + } // PrintObject by its ObjectID, to be used to uniquely bind slicing warnings to their source PrintObjects // in the notification center. const PrintObject* get_object(ObjectID object_id) const { diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 72a938ab33..67c6298f89 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -2,6 +2,7 @@ #include "libslic3r/BuildVolume.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/Geometry/ConvexHull.hpp" @@ -79,7 +80,6 @@ void ArrangeJob::clear_input() m_selected.reserve(count + 1 /* for optional wti */); m_unselected.reserve(count + 1 /* for optional wti */); m_unprintable.reserve(cunprint /* for optional wti */); - m_min_inflation = 0; } void ArrangeJob::prepare_all() { @@ -151,8 +151,7 @@ void ArrangeJob::prepare_selected() { static void update_arrangepoly_slaprint(arrangement::ArrangePolygon &ret, const SLAPrintObject &po, - const ModelInstance &inst, - coord_t min_infl) + const ModelInstance &inst) { auto laststep = po.last_completed_step(); @@ -189,10 +188,22 @@ static void update_arrangepoly_slaprint(arrangement::ArrangePolygon &ret, po.config().pad_around_object.getBool() * po.config().pad_object_gap.getFloat()); - ret.inflation = std::max(infl, min_infl); + ret.inflation = 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); @@ -204,27 +215,40 @@ arrangement::ArrangePolygon ArrangeJob::get_arrange_poly_(ModelInstance *mi) m_unarranged.emplace_back(mi); }; - if (m_plater->printer_technology() == ptSLA) { - auto obj_id = mi->get_object()->id(); - const SLAPrintObject *po = - m_plater->sla_print().get_print_object_by_model_object_id(obj_id); + return ap; +} - if (po) { - update_arrangepoly_slaprint(ap, *po, *mi, m_min_inflation); - m_min_inflation = std::max(m_min_inflation, ap.inflation); - } - } else { - // TODO: get fff supports outline +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(); + skirt_inset = print.config().skirts.value * print.skirt_flow().width() + + print.config().skirt_distance.value; } - return ap; + return scaled(skirt_inset); } void ArrangeJob::prepare() { wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all(); + + coord_t min_offset = 0; for (auto &ap : m_selected) { - ap.inflation = m_min_inflation; + 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; } } @@ -238,6 +262,8 @@ void ArrangeJob::process(Ctl &ctl) prepare(); params = get_arrange_params(m_plater); get_bed_shape(*m_plater->config(), bed); + 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()); @@ -352,6 +378,23 @@ arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst, { 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; } diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp index 4b75868a03..a422d44070 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp @@ -21,7 +21,7 @@ class ArrangeJob : public Job ArrangePolygons m_selected, m_unselected, m_unprintable; std::vector m_unarranged; - coord_t m_min_inflation = 0; + coord_t m_min_bed_inset = 0.; Plater *m_plater; @@ -104,6 +104,8 @@ arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst, arrangement::ArrangeParams get_arrange_params(Plater *p); +coord_t get_skirt_offset(const Plater* plater); + }} // namespace Slic3r::GUI #endif // ARRANGEJOB_HPP diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp index 6a173683f6..b8a579ed5d 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.cpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.cpp @@ -18,6 +18,7 @@ void FillBedJob::prepare() m_selected.clear(); m_unselected.clear(); m_bedpts.clear(); + m_min_bed_inset = 0.; m_object_idx = m_plater->get_selected_object_idx(); if (m_object_idx == -1) @@ -29,7 +30,7 @@ void FillBedJob::prepare() m_selected.reserve(model_object->instances.size()); for (ModelInstance *inst : model_object->instances) if (inst->printable) { - ArrangePolygon ap = get_arrange_poly(PtrWrapper{inst}, m_plater); + 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; @@ -101,6 +102,23 @@ void FillBedJob::prepare() for (auto &p : m_unselected) if (p.bed_idx > 0) p.translation(X) -= p.bed_idx * stride; + + 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; + } } void FillBedJob::process(Ctl &ctl) @@ -110,6 +128,8 @@ void FillBedJob::process(Ctl &ctl) 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); diff --git a/src/slic3r/GUI/Jobs/FillBedJob.hpp b/src/slic3r/GUI/Jobs/FillBedJob.hpp index b1417bbbd4..b953e0e981 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.hpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.hpp @@ -16,6 +16,7 @@ class FillBedJob : public Job ArrangePolygons m_selected; ArrangePolygons m_unselected; + coord_t m_min_bed_inset = 0.; Points m_bedpts; From 333d0727efc0f66235c1bbf4269c105d80b6cfb4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 20 Feb 2023 14:10:23 +0100 Subject: [PATCH 3/4] Handling brim offset in SLA without the need to slice the object --- src/slic3r/GUI/Jobs/ArrangeJob.cpp | 48 +++++++++++++++++------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 67c6298f89..8115136a57 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -97,28 +97,28 @@ void ArrangeJob::prepare_all() { void ArrangeJob::prepare_selected() { clear_input(); - + Model &model = m_plater->model(); double stride = bed_stride(m_plater); - + 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); @@ -127,11 +127,11 @@ void ArrangeJob::prepare_selected() { (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); @@ -139,20 +139,34 @@ void ArrangeJob::prepare_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); - + 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; + for (auto &p : m_unselected) + p.translation(X) -= p.bed_idx * stride; } 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) { @@ -181,15 +195,9 @@ static void update_arrangepoly_slaprint(arrangement::ArrangePolygon &ret, polys.emplace_back(its_convex_hull_2d_above(smesh.its, trafo_instance, zlvl)); ret.poly.contour = Geometry::convex_hull(polys); ret.poly.holes = {}; - - // 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 infl = 1.1 * scaled(po.config().pad_brim_size.getFloat() + - po.config().pad_around_object.getBool() * - po.config().pad_object_gap.getFloat()); - - ret.inflation = infl; } + + ret.inflation = pad_infl; } static coord_t brim_offset(const PrintObject &po, const ModelInstance &inst) From 92e0c1351319d2a8e2d4f5c399d3694a85e1be2d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 20 Feb 2023 14:10:59 +0100 Subject: [PATCH 4/4] Try to handle fff supports in arrange after slicing Not really precise yet --- src/slic3r/GUI/Jobs/ArrangeJob.cpp | 45 +++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 8115136a57..826dc24076 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -212,6 +212,49 @@ static coord_t brim_offset(const PrintObject &po, const ModelInstance &inst) return has_outer_brim ? scaled(brim_width + brim_separation) : 0; } +template +Polygon support_layers_chull (Points &pts, It from_lyr, It to_lyr) { + + size_t cap = 0; + for (auto it = from_lyr; it != to_lyr; ++it) { + for (const ExPolygon &expoly : (*it)->support_islands) + cap += expoly.contour.points.size(); + } + + pts.reserve(pts.size() + cap); + + for (auto it = from_lyr; it != to_lyr; ++it) { + for (const ExPolygon &expoly : (*it)->support_islands) + std::copy(expoly.contour.begin(), expoly.contour.end(), + std::back_inserter(pts)); + } + + Polygon ret = Geometry::convex_hull(pts); + + return ret; +} + +static void update_arrangepoly_fffprint(arrangement::ArrangePolygon &ret, + const PrintObject &po, + const ModelInstance &inst) +{ + auto laststep = po.last_completed_step(); + + coord_t infl = brim_offset(po, inst); + + if (laststep < posCount && laststep > posSupportMaterial) { + Points pts = std::move(ret.poly.contour.points); + Polygon poly = support_layers_chull(pts, + po.support_layers().begin(), + po.support_layers().end()); + + ret.poly.contour = std::move(poly); + ret.poly.holes = {}; + } + + ret.inflation = infl; +} + arrangement::ArrangePolygon ArrangeJob::get_arrange_poly_(ModelInstance *mi) { arrangement::ArrangePolygon ap = get_arrange_poly(mi, m_plater); @@ -399,7 +442,7 @@ arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst, plater->fff_print().get_print_object_by_model_object_id(obj_id); if (po) { - ap.inflation = brim_offset(*po, *inst); + update_arrangepoly_fffprint(ap, *po, *inst); } }