From d5bdaceff200bab6ba06c0ef5f9109ab03c79732 Mon Sep 17 00:00:00 2001
From: tamasmeszaros <meszaros.q@gmail.com>
Date: Wed, 18 Nov 2020 00:06:34 +0100
Subject: [PATCH 1/4] Right mouse reaction to arrange button

Working arrange settings popup
---
 src/libslic3r/Arrange.cpp          | 34 +++++++++++++---------------
 src/libslic3r/Arrange.hpp          |  2 ++
 src/slic3r/GUI/GLCanvas3D.cpp      | 36 +++++++++++++++++++++++++++++-
 src/slic3r/GUI/GLCanvas3D.hpp      | 12 ++++++++++
 src/slic3r/GUI/Jobs/ArrangeJob.cpp |  8 ++++---
 5 files changed, 69 insertions(+), 23 deletions(-)

diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp
index e61f11517..00656a629 100644
--- a/src/libslic3r/Arrange.cpp
+++ b/src/libslic3r/Arrange.cpp
@@ -83,7 +83,7 @@ const double BIG_ITEM_TRESHOLD = 0.02;
 // Fill in the placer algorithm configuration with values carefully chosen for
 // Slic3r.
 template<class PConf>
-void fill_config(PConf& pcfg) {
+void fill_config(PConf& pcfg, const ArrangeParams &params) {
 
     // Align the arranged pile into the center of the bin
     pcfg.alignment = PConf::Alignment::CENTER;
@@ -93,14 +93,17 @@ void fill_config(PConf& pcfg) {
 
     // TODO cannot use rotations until multiple objects of same geometry can
     // handle different rotations.
-    pcfg.rotations = { 0.0 };
+    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 = 0.65f;
+    pcfg.accuracy = params.accuracy;
     
     // Allow parallel execution.
-    pcfg.parallel = true;
+    pcfg.parallel = params.parallel;
 }
 
 // Apply penalty to object function result. This is used only when alignment
@@ -304,15 +307,15 @@ protected:
     
 public:
     AutoArranger(const TBin &                  bin,
-                 Distance                      dist,
+                 const ArrangeParams           &params,
                  std::function<void(unsigned)> progressind,
                  std::function<bool(void)>     stopcond)
-        : m_pck(bin, dist)
+        : 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);
+        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).
@@ -349,12 +352,6 @@ public:
         
         m_pck.configure(m_pconf);
     }
-    
-    AutoArranger(const TBin &                  bin,
-                 std::function<void(unsigned)> progressind,
-                 std::function<bool(void)>     stopcond)
-        : AutoArranger{bin, 0 /* no min distance */, progressind, stopcond}
-    {}
      
     template<class It> inline void operator()(It from, It to) {
         m_rtree.clear();
@@ -457,7 +454,7 @@ void _arrange(
         std::vector<Item> &           shapes,
         std::vector<Item> &           excludes,
         const BinT &                  bin,
-        const ArrangeParams &         params,
+        const ArrangeParams           &params,
         std::function<void(unsigned)> progressfn,
         std::function<bool()>         stopfn)
 {
@@ -467,11 +464,10 @@ void _arrange(
     
     auto corrected_bin = bin;
     sl::offset(corrected_bin, md);
-    
-    AutoArranger<BinT> arranger{corrected_bin, progressfn, stopfn};
-    
-    arranger.config().accuracy = params.accuracy;
-    arranger.config().parallel = params.parallel;
+    ArrangeParams mod_params = params;
+    mod_params.min_obj_distance = 0;
+
+    AutoArranger<BinT> 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);
diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp
index 7630ab3e8..65c3984d5 100644
--- a/src/libslic3r/Arrange.hpp
+++ b/src/libslic3r/Arrange.hpp
@@ -78,6 +78,8 @@ struct ArrangeParams {
     
     /// Allow parallel execution.
     bool parallel = true;
+
+    bool allow_rotations = false;
     
     /// Progress indicator callback called when an object gets packed. 
     /// The unsigned argument is the number of items remaining to pack.
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index cd82878c2..ae08d40d9 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -170,7 +170,7 @@ void GLCanvas3D::LayersEditing::init()
 }
 
 void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config)
-{ 
+{
     m_config = config;
     delete m_slicing_parameters;
     m_slicing_parameters = nullptr;
@@ -1325,6 +1325,9 @@ void GLCanvas3D::update_instance_printable_state_for_objects(std::vector<size_t>
 
 void GLCanvas3D::set_config(const DynamicPrintConfig* config)
 {
+    if (!m_config)
+        m_arrange_settings.distance = min_object_distance(*config);
+
     m_config = config;
     m_layers_editing.set_config(config);
 }
@@ -3847,6 +3850,30 @@ bool GLCanvas3D::_render_search_list(float pos_x) const
     return action_taken;
 }
 
+bool GLCanvas3D::_render_arrange_popup()
+{
+    ImGuiWrapper *imgui = wxGetApp().imgui();
+
+    float x = 0.5f * (float)get_canvas_size().get_width();
+    imgui->set_next_window_pos(x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f);
+
+    imgui->begin(_(L("Arrange options")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
+    ArrangeSettings settings = m_arrange_settings;
+
+    if (imgui->slider_float(_(L("Gap size")), &settings.distance, 0.f, 100.f))
+        m_arrange_settings.distance = settings.distance;
+
+    if (imgui->slider_float(_(L("Accuracy")), &settings.accuracy, 0.f, 1.f))
+        m_arrange_settings.accuracy = settings.accuracy;
+
+    if (imgui->checkbox(_(L("Enable rotations")), settings.enable_rotation))
+        m_arrange_settings.enable_rotation = settings.enable_rotation;
+
+    imgui->end();
+
+    return false;
+}
+
 #define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0
 #if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
 static void debug_output_thumbnail(const ThumbnailData& thumbnail_data)
@@ -4263,6 +4290,13 @@ bool GLCanvas3D::_init_main_toolbar()
     item.sprite_id = 3;
     item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); };
     item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
+    item.right.toggable = true;
+    item.right.render_callback = [this](float left, float right, float, float) {
+        if (m_canvas != nullptr)
+        {
+            _render_arrange_popup();
+        }
+    };
     if (!m_main_toolbar.add_item(item))
         return false;
 
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index 0975e59a0..c760dc323 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -381,6 +381,13 @@ public:
         Cross
     };
 
+    struct ArrangeSettings
+    {
+        float distance         = 6.;
+        float accuracy         = 0.65f;
+        bool  enable_rotation  = false;
+    };
+
 private:
     wxGLCanvas* m_canvas;
     wxGLContext* m_context;
@@ -452,6 +459,8 @@ private:
     mutable bool m_tooltip_enabled{ true };
     Slope m_slope;
 
+    ArrangeSettings m_arrange_settings;
+
 public:
     explicit GLCanvas3D(wxGLCanvas* canvas);
     ~GLCanvas3D();
@@ -671,6 +680,8 @@ public:
     void use_slope(bool use) { m_slope.use(use); }
     void set_slope_normal_angle(float angle_in_deg) { m_slope.set_normal_angle(angle_in_deg); }
 
+    const ArrangeSettings& get_arrange_settings() const { return m_arrange_settings; }
+
 private:
     bool _is_shown_on_screen() const;
 
@@ -717,6 +728,7 @@ private:
     void _render_selection_sidebar_hints() const;
     bool _render_undo_redo_stack(const bool is_undo, float pos_x) const;
     bool _render_search_list(float pos_x) const;
+    bool _render_arrange_popup();
     void _render_thumbnail_internal(ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
     // render thumbnail using an off-screen framebuffer
     void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp
index 41fd717da..9bb26e096 100644
--- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp
+++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp
@@ -147,11 +147,13 @@ void ArrangeJob::prepare()
 void ArrangeJob::process()
 {
     static const auto arrangestr = _(L("Arranging"));
-    
-    double dist = min_object_distance(*m_plater->config());
+
+    GLCanvas3D::ArrangeSettings settings =
+        m_plater->canvas3D()->get_arrange_settings();
     
     arrangement::ArrangeParams params;
-    params.min_obj_distance = scaled(dist);
+    params.min_obj_distance = scaled(settings.distance);
+    params.allow_rotations  = settings.enable_rotation;
     
     auto count = unsigned(m_selected.size() + m_unprintable.size());
     Points bedpts = get_bed_shape(*m_plater->config());

From e17e6b4d0e6974eb560ee8c86dff1a28f91d271a Mon Sep 17 00:00:00 2001
From: tamasmeszaros <meszaros.q@gmail.com>
Date: Fri, 20 Nov 2020 09:28:09 +0100
Subject: [PATCH 2/4] Add minimum bounding box rotation as starting point

---
 src/libslic3r/Arrange.cpp | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp
index 00656a629..e581f4c0f 100644
--- a/src/libslic3r/Arrange.cpp
+++ b/src/libslic3r/Arrange.cpp
@@ -7,6 +7,7 @@
 #include <libnest2d/optimizers/nlopt/subplex.hpp>
 #include <libnest2d/placers/nfpplacer.hpp>
 #include <libnest2d/selections/firstfit.hpp>
+#include <libnest2d/utils/rotcalipers.hpp>
 
 #include <numeric>
 #include <ClipperUtils.hpp>
@@ -449,6 +450,12 @@ template<class Bin> void remove_large_items(std::vector<Item> &items, Bin &&bin)
             ++it : it = items.erase(it);
 }
 
+template<class S> Radians min_area_boundingbox_rotation(const S &sh)
+{
+    return minAreaBoundingBox<S, TCompute<S>, boost::rational<LargeInt>>(sh)
+        .angleToX();
+}
+
 template<class BinT> // Arrange for arbitrary bin type
 void _arrange(
         std::vector<Item> &           shapes,
@@ -483,6 +490,13 @@ void _arrange(
     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()));
+
     arranger(inp.begin(), inp.end());
     for (Item &itm : inp) itm.inflate(-infl);
 }

From bc3696bd42ff5fe7dbf4589e9b403cdd2af050f8 Mon Sep 17 00:00:00 2001
From: tamasmeszaros <meszaros.q@gmail.com>
Date: Wed, 18 Nov 2020 14:38:46 +0100
Subject: [PATCH 3/4] Save and load arrange settings to app_config

---
 src/slic3r/GUI/GLCanvas3D.cpp | 44 ++++++++++++++++++++++++++---------
 src/slic3r/GUI/GLCanvas3D.hpp |  2 +-
 2 files changed, 34 insertions(+), 12 deletions(-)

diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index ae08d40d9..7bab21982 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -1091,6 +1091,25 @@ wxDEFINE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent);
 
 const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25;
 
+GLCanvas3D::ArrangeSettings load_arrange_settings()
+{
+    GLCanvas3D::ArrangeSettings settings;
+
+    std::string dist_str =
+        wxGetApp().app_config->get("arrange", "min_object_distance");
+
+    std::string en_rot_str =
+        wxGetApp().app_config->get("arrange", "enable_rotation");
+
+    if (!dist_str.empty())
+        settings.distance = std::stof(dist_str);
+
+    if (!en_rot_str.empty())
+        settings.enable_rotation = (en_rot_str == "1" || en_rot_str == "yes");
+
+    return settings;
+}
+
 GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
     : m_canvas(canvas)
     , m_context(nullptr)
@@ -1133,6 +1152,8 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
 #endif // ENABLE_RETINA_GL
     }
 
+    m_arrange_settings = load_arrange_settings();
+
     m_selection.set_volumes(&m_volumes.volumes);
 }
 
@@ -1325,9 +1346,6 @@ void GLCanvas3D::update_instance_printable_state_for_objects(std::vector<size_t>
 
 void GLCanvas3D::set_config(const DynamicPrintConfig* config)
 {
-    if (!m_config)
-        m_arrange_settings.distance = min_object_distance(*config);
-
     m_config = config;
     m_layers_editing.set_config(config);
 }
@@ -3850,7 +3868,7 @@ bool GLCanvas3D::_render_search_list(float pos_x) const
     return action_taken;
 }
 
-bool GLCanvas3D::_render_arrange_popup()
+void GLCanvas3D:: _render_arrange_popup()
 {
     ImGuiWrapper *imgui = wxGetApp().imgui();
 
@@ -3860,18 +3878,19 @@ bool GLCanvas3D::_render_arrange_popup()
     imgui->begin(_(L("Arrange options")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
     ArrangeSettings settings = m_arrange_settings;
 
-    if (imgui->slider_float(_(L("Gap size")), &settings.distance, 0.f, 100.f))
+    auto &appcfg = wxGetApp().app_config;
+
+    if (imgui->slider_float(_(L("Gap size")), &settings.distance, 0.f, 100.f)) {
         m_arrange_settings.distance = settings.distance;
+        appcfg->set("arrange", "min_object_distance", std::to_string(settings.distance));
+    }
 
-    if (imgui->slider_float(_(L("Accuracy")), &settings.accuracy, 0.f, 1.f))
-        m_arrange_settings.accuracy = settings.accuracy;
-
-    if (imgui->checkbox(_(L("Enable rotations")), settings.enable_rotation))
+    if (imgui->checkbox(_(L("Enable rotations")), settings.enable_rotation)) {
         m_arrange_settings.enable_rotation = settings.enable_rotation;
+        appcfg->set("arrange", "enable_rotation", "1");
+    }
 
     imgui->end();
-
-    return false;
 }
 
 #define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0
@@ -4300,6 +4319,9 @@ bool GLCanvas3D::_init_main_toolbar()
     if (!m_main_toolbar.add_item(item))
         return false;
 
+    item.right.toggable = false;
+    item.right.render_callback = GLToolbarItem::Default_Render_Callback;
+
     if (!m_main_toolbar.add_separator())
         return false;
 
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index c760dc323..7a73f8fe4 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -728,7 +728,7 @@ private:
     void _render_selection_sidebar_hints() const;
     bool _render_undo_redo_stack(const bool is_undo, float pos_x) const;
     bool _render_search_list(float pos_x) const;
-    bool _render_arrange_popup();
+    void _render_arrange_popup();
     void _render_thumbnail_internal(ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
     // render thumbnail using an off-screen framebuffer
     void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;

From adf81af6de25fe6a0f4eacb33dc17cfed7ed0670 Mon Sep 17 00:00:00 2001
From: tamasmeszaros <meszaros.q@gmail.com>
Date: Fri, 20 Nov 2020 09:32:18 +0100
Subject: [PATCH 4/4] Prototype feature: fill plater with instances of selected
 object

#fixes #1350
---
 src/libslic3r/Arrange.cpp          |  39 ++++----
 src/slic3r/CMakeLists.txt          |   2 +
 src/slic3r/GUI/Jobs/ArrangeJob.cpp |  34 ++++---
 src/slic3r/GUI/Jobs/ArrangeJob.hpp |  57 ++++++------
 src/slic3r/GUI/Jobs/FillBedJob.cpp | 139 +++++++++++++++++++++++++++++
 src/slic3r/GUI/Jobs/FillBedJob.hpp |  46 ++++++++++
 src/slic3r/GUI/Plater.cpp          |  26 +++++-
 src/slic3r/GUI/Plater.hpp          |   2 +
 8 files changed, 286 insertions(+), 59 deletions(-)
 create mode 100644 src/slic3r/GUI/Jobs/FillBedJob.cpp
 create mode 100644 src/slic3r/GUI/Jobs/FillBedJob.hpp

diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp
index e581f4c0f..e4a5c7b42 100644
--- a/src/libslic3r/Arrange.cpp
+++ b/src/libslic3r/Arrange.cpp
@@ -566,28 +566,35 @@ static void process_arrangeable(const ArrangePolygon &arrpoly,
     outp.back().priority(arrpoly.priority);
 }
 
+template<class Fn> 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(bb);
+        else if (!std::isnan(circ.radius()))
+            return fn(circ);
+        else
+            return fn(Polygon(bed));
+    }
+}
+
 template<>
 void arrange(ArrangePolygons &      items,
              const ArrangePolygons &excludes,
              const Points &         bed,
              const ArrangeParams &  params)
 {
-    if (bed.empty())
-        arrange(items, excludes, InfiniteBed{}, params);
-    else if (bed.size() == 1)
-        arrange(items, excludes, InfiniteBed{bed.front()}, params);
-    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)
-            arrange(items, excludes, bb, params);
-        else if (!std::isnan(circ.radius()))
-            arrange(items, excludes, circ, params);
-        else
-            arrange(items, excludes, Polygon(bed), params);   
-    }
+    call_with_bed(bed, [&](const auto &bin) {
+        arrange(items, excludes, bin, params);
+    });
 }
 
 template<class BedT>
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index c44b76970..699135d27 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -162,6 +162,8 @@ set(SLIC3R_GUI_SOURCES
     GUI/Jobs/ArrangeJob.cpp
     GUI/Jobs/RotoptimizeJob.hpp
     GUI/Jobs/RotoptimizeJob.cpp
+    GUI/Jobs/FillBedJob.hpp
+    GUI/Jobs/FillBedJob.cpp
     GUI/Jobs/SLAImportJob.hpp
     GUI/Jobs/SLAImportJob.cpp
     GUI/Jobs/ProgressIndicator.hpp
diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp
index 9bb26e096..b8f165c02 100644
--- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp
+++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp
@@ -46,7 +46,7 @@ public:
     }
 };
 
-static WipeTower get_wipe_tower(Plater &plater)
+static WipeTower get_wipe_tower(const Plater &plater)
 {
     return WipeTower{plater.canvas3D()->get_wipe_tower_info()};
 }
@@ -68,18 +68,13 @@ void ArrangeJob::clear_input()
     m_unprintable.reserve(cunprint /* for optional wti */);
 }
 
-double ArrangeJob::bed_stride() const {
-    double bedwidth = m_plater->bed_shape_bb().size().x();
-    return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
-}
-
 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));
+            cont.emplace_back(get_arrange_poly(mi, m_plater));
         }
 
     if (auto wti = get_wipe_tower(*m_plater))
@@ -90,7 +85,7 @@ void ArrangeJob::prepare_selected() {
     clear_input();
     
     Model &model = m_plater->model();
-    double stride = bed_stride();
+    double stride = bed_stride(m_plater);
     
     std::vector<const Selection::InstanceIdxsList *>
             obj_sel(model.objects.size(), nullptr);
@@ -111,7 +106,7 @@ void ArrangeJob::prepare_selected() {
                 inst_sel[size_t(inst_id)] = true;
         
         for (size_t i = 0; i < inst_sel.size(); ++i) {
-            ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
+            ArrangePolygon &&ap = get_arrange_poly(mo->instances[i], m_plater);
             
             ArrangePolygons &cont = mo->instances[i]->printable ?
                         (inst_sel[i] ? m_selected :
@@ -123,7 +118,7 @@ void ArrangeJob::prepare_selected() {
     }
     
     if (auto wti = get_wipe_tower(*m_plater)) {
-        ArrangePolygon &&ap = get_arrange_poly(&wti);
+        ArrangePolygon &&ap = get_arrange_poly(&wti, m_plater);
         
         m_plater->get_selection().is_wipe_tower() ?
                     m_selected.emplace_back(std::move(ap)) :
@@ -213,14 +208,25 @@ void ArrangeJob::finalize() {
     Job::finalize();
 }
 
-arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &plater)
+std::optional<arrangement::ArrangePolygon>
+get_wipe_tower_arrangepoly(const Plater &plater)
 {
-    return WipeTower{plater.canvas3D()->get_wipe_tower_info()}.get_arrange_polygon();
+    if (auto wti = get_wipe_tower(plater))
+        return wti.get_arrange_polygon();
+
+    return {};
 }
 
-void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap)
+void apply_wipe_tower_arrangepoly(Plater &                           plater,
+                                  const arrangement::ArrangePolygon &ap)
 {
-    WipeTower{plater.canvas3D()->get_wipe_tower_info()}.apply_arrange_result(ap.translation.cast<double>(), ap.rotation);
+    WipeTower{plater.canvas3D()->get_wipe_tower_info()}
+        .apply_arrange_result(ap.translation.cast<double>(), ap.rotation);
+}
+
+double bed_stride(const Plater *plater) {
+    double bedwidth = plater->bed_shape_bb().size().x();
+    return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
 }
 
 }} // namespace Slic3r::GUI
diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp
index bd097af6b..f178711e9 100644
--- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp
+++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp
@@ -14,35 +14,12 @@ class ArrangeJob : public Job
     
     using ArrangePolygon = arrangement::ArrangePolygon;
     using ArrangePolygons = arrangement::ArrangePolygons;
-    
-    // The gap between logical beds in the x axis expressed in ratio of
-    // the current bed width.
-    static const constexpr double LOGICAL_BED_GAP = 1. / 5.;
-    
+
     ArrangePolygons m_selected, m_unselected, m_unprintable;
     
     // clear m_selected and m_unselected, reserve space for next usage
     void clear_input();
-    
-    // Stride between logical beds
-    double bed_stride() const;
-    
-    // Set up arrange polygon for a ModelInstance and Wipe tower
-    template<class T> ArrangePolygon get_arrange_poly(T *obj) const
-    {
-        ArrangePolygon ap = obj->get_arrange_polygon();
-        ap.priority       = 0;
-        ap.bed_idx        = ap.translation.x() / bed_stride();
-        ap.setter         = [obj, this](const ArrangePolygon &p) {
-            if (p.is_arranged()) {
-                Vec2d t = p.translation.cast<double>();
-                t.x() += p.bed_idx * bed_stride();
-                obj->apply_arrange_result(t, p.rotation);
-            }
-        };
-        return ap;
-    }
-    
+
     // Prepare all objects on the bed regardless of the selection
     void prepare_all();
     
@@ -69,9 +46,37 @@ public:
     void finalize() override;
 };
 
-arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &);
+std::optional<arrangement::ArrangePolygon> get_wipe_tower_arrangepoly(const Plater &);
 void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap);
 
+// 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);
+
+// Set up arrange polygon for a ModelInstance and Wipe tower
+template<class T>
+arrangement::ArrangePolygon get_arrange_poly(T *obj, const Plater *plater)
+{
+    using ArrangePolygon = arrangement::ArrangePolygon;
+
+    ArrangePolygon ap = obj->get_arrange_polygon();
+    ap.priority       = 0;
+    ap.bed_idx        = ap.translation.x() / bed_stride(plater);
+    ap.setter         = [obj, plater](const ArrangePolygon &p) {
+        if (p.is_arranged()) {
+            Vec2d t = p.translation.cast<double>();
+            t.x() += p.bed_idx * bed_stride(plater);
+            obj->apply_arrange_result(t, p.rotation);
+        }
+    };
+
+    return ap;
+}
+
+
 }} // namespace Slic3r::GUI
 
 #endif // ARRANGEJOB_HPP
diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp
new file mode 100644
index 000000000..8c7b3c418
--- /dev/null
+++ b/src/slic3r/GUI/Jobs/FillBedJob.cpp
@@ -0,0 +1,139 @@
+#include "FillBedJob.hpp"
+
+#include "libslic3r/Model.hpp"
+#include "libslic3r/ClipperUtils.hpp"
+
+#include "slic3r/GUI/Plater.hpp"
+#include "slic3r/GUI/GLCanvas3D.hpp"
+#include "slic3r/GUI/GUI_ObjectList.hpp"
+
+#include <numeric>
+
+namespace Slic3r {
+namespace GUI {
+
+void FillBedJob::prepare()
+{
+    m_selected.clear();
+    m_unselected.clear();
+    m_bedpts.clear();
+
+    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);
+            ++ap.priority; // need to be included in the result
+            m_selected.emplace_back(ap);
+        }
+
+    if (m_selected.empty()) return;
+
+    m_bedpts = get_bed_shape(*m_plater->config());
+
+    auto &objects = m_plater->model().objects;
+    for (size_t idx = 0; idx < objects.size(); ++idx)
+        if (int(idx) != m_object_idx)
+            for (const ModelInstance *mi : objects[idx]->instances) {
+                m_unselected.emplace_back(mi->get_arrange_polygon());
+                m_unselected.back().bed_idx = 0;
+            }
+
+    if (auto wt = get_wipe_tower_arrangepoly(*m_plater))
+        m_unselected.emplace_back(std::move(*wt));
+
+    double sc = scaled<double>(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.poly.area();
+                                        }) / sc;
+
+    double fixed_area = unsel_area + m_selected.size() * poly_area;
+
+    // This is the maximum range, the real number will always be close but less.
+    double bed_area = Polygon{m_bedpts}.area() / sc;
+
+    m_status_range = (bed_area - fixed_area) / poly_area;
+
+    ModelInstance *mi = model_object->instances[0];
+    for (int i = 0; i < m_status_range; ++i) {
+        ArrangePolygon ap;
+        ap.poly = m_selected.front().poly;
+        ap.bed_idx = arrangement::UNARRANGED;
+        ap.setter = [this, mi](const ArrangePolygon &p) {
+            ModelObject *mo = m_plater->model().objects[m_object_idx];
+            ModelInstance *inst = mo->add_instance(*mi);
+            inst->apply_arrange_result(p.translation.cast<double>(), p.rotation);
+        };
+        m_selected.emplace_back(ap);
+    }
+}
+
+void FillBedJob::process()
+{
+    if (m_object_idx == -1 || m_selected.empty()) return;
+
+    GLCanvas3D::ArrangeSettings settings =
+        m_plater->canvas3D()->get_arrange_settings();
+
+    arrangement::ArrangeParams params;
+    params.min_obj_distance = scaled(settings.distance);
+    params.allow_rotations  = settings.enable_rotation;
+
+    params.stopcondition = [this]() { return was_canceled(); };
+
+    params.progressind = [this](unsigned st) {
+        if (st > 0)
+            update_status(int(m_status_range - st), _(L("Filling bed")));
+    };
+
+    arrangement::arrange(m_selected, m_unselected, m_bedpts, params);
+
+    // finalize just here.
+    update_status(m_status_range, was_canceled() ?
+                                      _(L("Bed filling canceled.")) :
+                                      _(L("Bed filling done.")));
+}
+
+void FillBedJob::finalize()
+{
+    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();
+
+    for (ArrangePolygon &ap : m_selected) {
+        if (ap.priority != 0 || !(ap.bed_idx == arrangement::UNARRANGED || ap.bed_idx > 0))
+            ap.apply();
+    }
+
+    model_object->ensure_on_bed();
+
+    m_plater->update();
+
+    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);
+                                     });
+
+    // FIXME: somebody explain why this is needed for increase_object_instances
+    if (inst_cnt == 1) added_cnt++;
+
+    if (added_cnt > 0)
+        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
new file mode 100644
index 000000000..448ae53d4
--- /dev/null
+++ b/src/slic3r/GUI/Jobs/FillBedJob.hpp
@@ -0,0 +1,46 @@
+#ifndef FILLBEDJOB_HPP
+#define FILLBEDJOB_HPP
+
+#include "ArrangeJob.hpp"
+
+namespace Slic3r { namespace GUI {
+
+class Plater;
+
+class FillBedJob : public Job
+{
+    Plater *m_plater;
+    int     m_object_idx = -1;
+
+    using ArrangePolygon  = arrangement::ArrangePolygon;
+    using ArrangePolygons = arrangement::ArrangePolygons;
+
+    ArrangePolygons m_selected;
+    ArrangePolygons m_unselected;
+
+    Points m_bedpts;
+
+    int m_status_range = 0;
+
+protected:
+
+    void prepare() override;
+
+public:
+    FillBedJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
+        : Job{std::move(pri)}, m_plater{plater}
+    {}
+
+    int status_range() const override
+    {
+        return m_status_range;
+    }
+
+    void process() override;
+
+    void finalize() override;
+};
+
+}} // namespace Slic3r::GUI
+
+#endif // FILLBEDJOB_HPP
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 46e1fe2fc..4b31f10d7 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -63,6 +63,7 @@
 #include "Mouse3DController.hpp"
 #include "Tab.hpp"
 #include "Jobs/ArrangeJob.hpp"
+#include "Jobs/FillBedJob.hpp"
 #include "Jobs/RotoptimizeJob.hpp"
 #include "Jobs/SLAImportJob.hpp"
 #include "BackgroundSlicingProcess.hpp"
@@ -1573,7 +1574,7 @@ struct Plater::priv
     class Jobs: public ExclusiveJobGroup
     {
         priv *m;
-        size_t m_arrange_id, m_rotoptimize_id, m_sla_import_id;
+        size_t m_arrange_id, m_fill_bed_id, m_rotoptimize_id, m_sla_import_id;
         
         void before_start() override { m->background_process.stop(); }
         
@@ -1581,6 +1582,7 @@ struct Plater::priv
         Jobs(priv *_m) : m(_m)
         {
             m_arrange_id = add_job(std::make_unique<ArrangeJob>(m->statusbar(), m->q));
+            m_fill_bed_id = add_job(std::make_unique<FillBedJob>(m->statusbar(), m->q));
             m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m->statusbar(), m->q));
             m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m->statusbar(), m->q));
         }
@@ -1590,6 +1592,12 @@ struct Plater::priv
             m->take_snapshot(_(L("Arrange")));
             start(m_arrange_id);
         }
+
+        void fill_bed()
+        {
+            m->take_snapshot(_(L("Fill bed")));
+            start(m_fill_bed_id);
+        }
         
         void optimize_rotation()
         {
@@ -2731,8 +2739,8 @@ void Plater::find_new_position(const ModelInstancePtrs &instances,
                 movable.emplace_back(std::move(arrpoly));
         }
     
-    if (p->view3D->get_canvas3d()->get_wipe_tower_info())
-        fixed.emplace_back(get_wipe_tower_arrangepoly(*this));
+    if (auto wt = get_wipe_tower_arrangepoly(*this))
+        fixed.emplace_back(*wt);
     
     arrangement::arrange(movable, fixed, get_bed_shape(*config()),
                          arrangement::ArrangeParams{min_d});
@@ -3860,6 +3868,8 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/
             [this](wxCommandEvent&) { q->decrease_instances();      }, "remove_copies",     nullptr, [this]() { return can_decrease_instances(); }, q);
         wxMenuItem* item_set_number_of_copies = append_menu_item(menu, wxID_ANY, _L("Set number of instances") + dots, _L("Change the number of instances of the selected object"),
             [this](wxCommandEvent&) { q->set_number_of_copies();    }, "number_of_copies",  nullptr, [this]() { return can_increase_instances(); }, q);
+        append_menu_item(menu, wxID_ANY, _L("Fill bed with instances") + dots, _L("Fill the remaining area of bed with instances of the selected object"),
+            [this](wxCommandEvent&) { q->fill_bed_with_instances();    }, "",  nullptr, [this]() { return can_increase_instances(); }, q);
 
 
         items_increase.push_back(item_increase);
@@ -4864,6 +4874,11 @@ void Plater::set_number_of_copies(/*size_t num*/)
         decrease_instances(-diff);
 }
 
+void Plater::fill_bed_with_instances()
+{
+    p->m_ui_jobs.fill_bed();
+}
+
 bool Plater::is_selection_empty() const
 {
     return p->get_selection().is_empty() || p->get_selection().is_wipe_tower();
@@ -5648,6 +5663,11 @@ GLCanvas3D* Plater::canvas3D()
     return p->view3D->get_canvas3d();
 }
 
+const GLCanvas3D* Plater::canvas3D() const
+{
+    return p->view3D->get_canvas3d();
+}
+
 GLCanvas3D* Plater::get_current_canvas3D()
 {
     return p->get_current_canvas3D();
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index 9a286c424..0c1d48bc3 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -181,6 +181,7 @@ public:
     void increase_instances(size_t num = 1);
     void decrease_instances(size_t num = 1);
     void set_number_of_copies(/*size_t num*/);
+    void fill_bed_with_instances();
     bool is_selection_empty() const;
     void scale_selection_to_fit_print_volume();
     void convert_unit(bool from_imperial_unit);
@@ -245,6 +246,7 @@ public:
     int get_selected_object_idx();
     bool is_single_full_object_selection() const;
     GLCanvas3D* canvas3D();
+    const GLCanvas3D * canvas3D() const;
     GLCanvas3D* get_current_canvas3D();
     BoundingBoxf bed_shape_bb() const;