Prototype feature: fill plater with instances of selected object

#fixes #1350
This commit is contained in:
tamasmeszaros 2020-11-20 09:32:18 +01:00
parent bc3696bd42
commit adf81af6de
8 changed files with 286 additions and 59 deletions

View File

@ -566,30 +566,37 @@ static void process_arrangeable(const ArrangePolygon &arrpoly,
outp.back().priority(arrpoly.priority);
}
template<>
void arrange(ArrangePolygons & items,
const ArrangePolygons &excludes,
const Points & bed,
const ArrangeParams & params)
template<class Fn> auto call_with_bed(const Points &bed, Fn &&fn)
{
if (bed.empty())
arrange(items, excludes, InfiniteBed{}, params);
return fn(InfiniteBed{});
else if (bed.size() == 1)
arrange(items, excludes, InfiniteBed{bed.front()}, params);
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)
arrange(items, excludes, bb, params);
return fn(bb);
else if (!std::isnan(circ.radius()))
arrange(items, excludes, circ, params);
return fn(circ);
else
arrange(items, excludes, Polygon(bed), params);
return fn(Polygon(bed));
}
}
template<>
void arrange(ArrangePolygons & items,
const ArrangePolygons &excludes,
const Points & bed,
const ArrangeParams & params)
{
call_with_bed(bed, [&](const auto &bin) {
arrange(items, excludes, bin, params);
});
}
template<class BedT>
void arrange(ArrangePolygons & arrangables,
const ArrangePolygons &excludes,

View File

@ -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

View File

@ -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

View File

@ -15,34 +15,11 @@ 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

View File

@ -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

View File

@ -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

View File

@ -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));
}
@ -1591,6 +1593,12 @@ struct Plater::priv
start(m_arrange_id);
}
void fill_bed()
{
m->take_snapshot(_(L("Fill bed")));
start(m_fill_bed_id);
}
void optimize_rotation()
{
m->take_snapshot(_(L("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();

View File

@ -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;