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 ¶ms) { // 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 ¶ms, 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 ¶ms, 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;