From adf81af6de25fe6a0f4eacb33dc17cfed7ed0670 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 20 Nov 2020 09:32:18 +0100 Subject: [PATCH] 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 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 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((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 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 +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(), ap.rotation); + WipeTower{plater.canvas3D()->get_wipe_tower_info()} + .apply_arrange_result(ap.translation.cast(), ap.rotation); +} + +double bed_stride(const Plater *plater) { + double bedwidth = plater->bed_shape_bb().size().x(); + return scaled((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 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(); - 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 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 +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(); + 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 + +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(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(), 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 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(m->statusbar(), m->q)); + m_fill_bed_id = add_job(std::make_unique(m->statusbar(), m->q)); m_rotoptimize_id = add_job(std::make_unique(m->statusbar(), m->q)); m_sla_import_id = add_job(std::make_unique(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;