diff options
Diffstat (limited to 'src/slic3r')
-rw-r--r-- | src/slic3r/GUI/GLCanvas3D.cpp | 7 | ||||
-rw-r--r-- | src/slic3r/GUI/GLCanvas3D.hpp | 2 | ||||
-rw-r--r-- | src/slic3r/GUI/Plater.cpp | 478 |
3 files changed, 284 insertions, 203 deletions
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 4e3093489..771d092a7 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5739,10 +5739,11 @@ const SLAPrint* GLCanvas3D::sla_print() const return (m_process == nullptr) ? nullptr : m_process->sla_print(); } -void GLCanvas3D::WipeTowerInfo::apply_arrange_result(Vec2d offset, double rotation_rads) +void GLCanvas3D::WipeTowerInfo::apply_arrange_result(Vec2crd off, double rotation_rads) { - m_pos = offset; - m_rotation = rotation_rads; + Vec2d offset = unscaled(off); + m_pos = offset; + m_rotation = rotation_rads; DynamicPrintConfig cfg; cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = m_pos(X); cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = m_pos(Y); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 2316637d8..a28791ed0 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -624,7 +624,7 @@ public: return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y()); } - void apply_arrange_result(Vec2d offset, double rotation_rads); + void apply_arrange_result(Vec2crd offset, double rotation_rads); arrangement::ArrangePolygon get_arrange_polygon() const { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3adfc0f05..f615c66d1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1272,147 +1272,290 @@ struct Plater::priv // objects would be frozen for the user. In case of arrange, an animation // could be shown, or with the optimize orientations, partial results // could be displayed. - class Job: public wxEvtHandler { - int m_range = 100; + class Job : public wxEvtHandler + { + int m_range = 100; std::future<void> m_ftr; - priv *m_plater = nullptr; - std::atomic<bool> m_running {false}, m_canceled {false}; - bool m_finalized = false; - - void run() { - m_running.store(true); process(); m_running.store(false); - + priv * m_plater = nullptr; + std::atomic<bool> m_running{false}, m_canceled{false}; + bool m_finalized = false; + + void run() + { + m_running.store(true); + process(); + m_running.store(false); + // ensure to call the last status to finalize the job update_status(status_range(), ""); } - + protected: - // status range for a particular job virtual int status_range() const { return 100; } - + // status update, to be used from the work thread (process() method) - void update_status(int st, const wxString& msg = "") { - auto evt = new wxThreadEvent(); evt->SetInt(st); evt->SetString(msg); - wxQueueEvent(this, evt); + void update_status(int st, const wxString &msg = "") + { + auto evt = new wxThreadEvent(); + evt->SetInt(st); + evt->SetString(msg); + wxQueueEvent(this, evt); } - - priv& plater() { return *m_plater; } - bool was_canceled() const { return m_canceled.load(); } - + + priv &plater() { return *m_plater; } + bool was_canceled() const { return m_canceled.load(); } + // Launched just before start(), a job can use it to prepare internals virtual void prepare() {} - - // Launched when the job is finished. It refreshes the 3dscene by def. - virtual void finalize() { + + // Launched when the job is finished. It refreshes the 3Dscene by def. + virtual void finalize() + { // Do a full refresh of scene tree, including regenerating // all the GLVolumes. FIXME The update function shall just // reload the modified matrices. - if(! was_canceled()) - plater().update(true); + if (!was_canceled()) plater().update(true); } - + public: - - Job(priv *_plater): m_plater(_plater) + Job(priv *_plater) : m_plater(_plater) { - Bind(wxEVT_THREAD, [this](const wxThreadEvent& evt){ + Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { auto msg = evt.GetString(); - if(! msg.empty()) plater().statusbar()->set_status_text(msg); - - if(m_finalized) return; - + if (!msg.empty()) + plater().statusbar()->set_status_text(msg); + + if (m_finalized) return; + plater().statusbar()->set_progress(evt.GetInt()); - if(evt.GetInt() == status_range()) { - + if (evt.GetInt() == status_range()) { // set back the original range and cancel callback plater().statusbar()->set_range(m_range); plater().statusbar()->set_cancel_callback(); wxEndBusyCursor(); - + finalize(); - + // dont do finalization again for the same process m_finalized = true; } }); } - - // TODO: use this when we all migrated to VS2019 - // Job(const Job&) = delete; - // Job(Job&&) = default; - // Job& operator=(const Job&) = delete; - // Job& operator=(Job&&) = default; - Job(const Job&) = delete; - Job& operator=(const Job&) = delete; - Job(Job &&o) : - m_range(o.m_range), - m_ftr(std::move(o.m_ftr)), - m_plater(o.m_plater), - m_finalized(o.m_finalized) - { - m_running.store(o.m_running.load()); - m_canceled.store(o.m_canceled.load()); - } - + + Job(const Job &) = delete; + Job(Job &&) = default; + Job &operator=(const Job &) = delete; + Job &operator=(Job &&) = default; + virtual void process() = 0; - - void start() { // Start the job. No effect if the job is already running - if(! m_running.load()) { - - prepare(); - + + void start() + { // Start the job. No effect if the job is already running + if (!m_running.load()) { + prepare(); + // Save the current status indicatior range and push the new one m_range = plater().statusbar()->get_range(); plater().statusbar()->set_range(status_range()); - + // init cancellation flag and set the cancel callback m_canceled.store(false); - plater().statusbar()->set_cancel_callback( [this](){ - m_canceled.store(true); - }); - + plater().statusbar()->set_cancel_callback( + [this]() { m_canceled.store(true); }); + m_finalized = false; - + // Changing cursor to busy wxBeginBusyCursor(); - - try { // Execute the job + + try { // Execute the job m_ftr = std::async(std::launch::async, &Job::run, this); - } catch(std::exception& ) { - update_status(status_range(), - _(L("ERROR: not enough resources to execute a new job."))); + } catch (std::exception &) { + update_status(status_range(), + _(L("ERROR: not enough resources to " + "execute a new job."))); } - + // The state changes will be undone when the process hits the // last status value, in the status update handler (see ctor) } } - - // To wait for the running job and join the threads. False is returned - // if the timeout has been reached and the job is still running. Call - // cancel() before this fn if you want to explicitly end the job. - bool join(int timeout_ms = 0) const { - if(!m_ftr.valid()) return true; - - if(timeout_ms <= 0) + + // To wait for the running job and join the threads. False is + // returned if the timeout has been reached and the job is still + // running. Call cancel() before this fn if you want to explicitly + // end the job. + bool join(int timeout_ms = 0) const + { + if (!m_ftr.valid()) return true; + + if (timeout_ms <= 0) m_ftr.wait(); - else if(m_ftr.wait_for(std::chrono::milliseconds(timeout_ms)) == - std::future_status::timeout) + else if (m_ftr.wait_for(std::chrono::milliseconds( + timeout_ms)) == std::future_status::timeout) return false; - + return true; } - + bool is_running() const { return m_running.load(); } void cancel() { m_canceled.store(true); } }; - + enum class Jobs : size_t { Arrange, Rotoptimize }; + class ArrangeJob : public Job + { + // 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.; + + // Cache the wti info + GLCanvas3D::WipeTowerInfo m_wti; + + // Cache the selected instances needed to write back the arrange + // result. The order of instances is the same as the arrange polys + struct IndexedArrangePolys { + ModelInstancePtrs insts; + arrangement::ArrangePolygons polys; + + void reserve(size_t cap) { insts.reserve(cap); polys.reserve(cap); } + void clear() { insts.clear(); polys.clear(); } + + void emplace_back(ModelInstance *inst) { + insts.emplace_back(inst); + polys.emplace_back(inst->get_arrange_polygon()); + } + + void swap(IndexedArrangePolys &pp) { + insts.swap(pp.insts); polys.swap(pp.polys); + } + }; + + IndexedArrangePolys m_selected, m_unselected; + + protected: + + void prepare() override + { + m_wti = plater().view3D->get_canvas3d()->get_wipe_tower_info(); + + // Get the selection map + Selection& sel = plater().get_selection(); + const Selection::ObjectIdxsToInstanceIdxsMap &selmap = + sel.get_content(); + + Model &model = plater().model; + + size_t count = 0; // To know how much space to reserve + for (auto obj : model.objects) count += obj->instances.size(); + + m_selected.clear(), m_unselected.clear(); + m_selected.reserve(count + 1 /* for optional wti */); + m_unselected.reserve(count + 1 /* for optional wti */); + + // Go through the objects and check if inside the selection + for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { + auto oit = selmap.find(int(oidx)); + + if (oit != selmap.end()) { // Object is selected + auto &iids = oit->second; + + // Go through instances and check if inside selection + size_t instcnt = model.objects[oidx]->instances.size(); + for (size_t iidx = 0; iidx < instcnt; ++iidx) { + auto instit = iids.find(iidx); + ModelInstance *oi = model.objects[oidx] + ->instances[iidx]; + + // Instance is selected + instit != iids.end() ? + m_selected.emplace_back(oi) : + m_unselected.emplace_back(oi); + } + } else // object not selected, all instances are unselected + for (ModelInstance *oi : model.objects[oidx]->instances) + m_unselected.emplace_back(oi); + } + + // If the selection is completely empty, consider all items as the + // selection + if (m_selected.insts.empty() && m_selected.polys.empty()) + m_selected.swap(m_unselected); + + if (m_wti) + sel.is_wipe_tower() ? + m_selected.polys.emplace_back(m_wti.get_arrange_polygon()) : + m_unselected.polys.emplace_back(m_wti.get_arrange_polygon()); + + // Stride between logical beds + double bedwidth = plater().bed_shape_bb().size().x(); + coord_t stride = scaled((1. + LOGICAL_BED_GAP) * bedwidth); + + for (arrangement::ArrangePolygon &ap : m_selected.polys) + if (ap.bed_idx > 0) ap.translation.x() -= ap.bed_idx * stride; + + for (arrangement::ArrangePolygon &ap : m_unselected.polys) + if (ap.bed_idx > 0) ap.translation.x() -= ap.bed_idx * stride; + } + + public: + using Job::Job; + + + int status_range() const override + { + return int(m_selected.polys.size()); + } + + void process() override; + + void finalize() override { + + if (was_canceled()) { // Ignore the arrange result if aborted. + Job::finalize(); + return; + } + + // Stride between logical beds + double bedwidth = plater().bed_shape_bb().size().x(); + coord_t stride = scaled((1. + LOGICAL_BED_GAP) * bedwidth); + + for(size_t i = 0; i < m_selected.insts.size(); ++i) { + if (m_selected.polys[i].bed_idx != arrangement::UNARRANGED) { + Vec2crd offs = m_selected.polys[i].translation; + double rot = m_selected.polys[i].rotation; + int bdidx = m_selected.polys[i].bed_idx; + offs.x() += bdidx * stride; + m_selected.insts[i]->apply_arrange_result(offs, rot, bdidx); + } + } + + // Handle the wipe tower + const arrangement::ArrangePolygon &wtipoly = m_selected.polys.back(); + if (m_wti && wtipoly.bed_idx != arrangement::UNARRANGED) { + Vec2crd o = wtipoly.translation; + double r = wtipoly.rotation; + o.x() += wtipoly.bed_idx * stride; + m_wti.apply_arrange_result(o, r); + } + + // Call original finalize (will update the scene) + Job::finalize(); + } + }; + + class RotoptimizeJob : public Job + { + public: + using Job::Job; + void process() override; + }; + // Jobs defined inside the group class will be managed so that only one can // run at a time. Also, the background process will be stopped if a job is // started. @@ -1422,84 +1565,8 @@ struct Plater::priv priv * m_plater; - class ArrangeJob : public Job - { - GLCanvas3D::WipeTowerInfo m_wti; - arrangement::ArrangePolygons m_selected, m_unselected; - - static std::array<arrangement::ArrangePolygons, 2> collect( - Model &model, const Selection &sel) - { - const Selection::ObjectIdxsToInstanceIdxsMap &selmap = - sel.get_content(); - - size_t count = 0; - for (auto obj : model.objects) count += obj->instances.size(); - - arrangement::ArrangePolygons selected, unselected; - selected.reserve(count + 1 /* for optional wti */); - unselected.reserve(count + 1 /* for optional wti */); - - for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { - auto oit = selmap.find(int(oidx)); - - if (oit != selmap.end()) { - auto &iids = oit->second; - - for (size_t iidx = 0; - iidx < model.objects[oidx]->instances.size(); - ++iidx) - { - auto instit = iids.find(iidx); - ModelInstance *inst = model.objects[oidx] - ->instances[iidx]; - instit == iids.end() ? - unselected.emplace_back(inst->get_arrange_polygon()) : - selected.emplace_back(inst->get_arrange_polygon()); - } - } else // object not selected, all instances are unselected - for (auto inst : model.objects[oidx]->instances) - unselected.emplace_back(inst->get_arrange_polygon()); - } - - if (selected.empty()) selected.swap(unselected); - - return {selected, unselected}; - } - - protected: - - void prepare() override - { - m_wti = plater().view3D->get_canvas3d()->get_wipe_tower_info(); - - const Selection& sel = plater().get_selection(); - BoundingBoxf bedbb(plater().bed.get_shape()); - auto arrinput = collect(plater().model, sel); - m_selected.swap(arrinput[0]); - m_unselected.swap(arrinput[1]); - - if (m_wti) - sel.is_wipe_tower() ? - m_selected.emplace_back(m_wti.get_arrange_polygon()) : - m_unselected.emplace_back(m_wti.get_arrange_polygon()); - } - - public: - using Job::Job; - int status_range() const override - { - return int(m_selected.size()); - } - void process() override; - } arrange_job{m_plater}; - - class RotoptimizeJob : public Job - { - public: - using Job::Job; - void process() override; - } rotoptimize_job{m_plater}; + ArrangeJob arrange_job{m_plater}; + RotoptimizeJob rotoptimize_job{m_plater}; // To create a new job, just define a new subclass of Job, implement // the process and the optional prepare() and finalize() methods @@ -2447,50 +2514,47 @@ void Plater::priv::sla_optimize_rotation() { } arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const { - arrangement::BedShapeHint bedshape; const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape"); assert(bed_shape_opt); - if (bed_shape_opt) { - auto &bedpoints = bed_shape_opt->values; - Polyline bedpoly; bedpoly.points.reserve(bedpoints.size()); - for (auto &v : bedpoints) bedpoly.append(scaled(v)); - bedshape = arrangement::bedShape(bedpoly); - } + if (!bed_shape_opt) return {}; - return bedshape; + auto &bedpoints = bed_shape_opt->values; + Polyline bedpoly; bedpoly.points.reserve(bedpoints.size()); + for (auto &v : bedpoints) bedpoly.append(scaled(v)); + + return arrangement::BedShapeHint(bedpoly); } -void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { - auto count = unsigned(m_selected.size()); - plater().model.arrange_objects(6.f, nullptr); -// static const auto arrangestr = _(L("Arranging")); +void Plater::priv::ArrangeJob::process() { + static const auto arrangestr = _(L("Arranging")); -// // FIXME: I don't know how to obtain the minimum distance, it depends -// // on printer technology. I guess the following should work but it crashes. -// double dist = 6; // PrintConfig::min_object_distance(config); -// if (plater().printer_technology == ptFFF) { -// dist = PrintConfig::min_object_distance(plater().config); -// } + // FIXME: I don't know how to obtain the minimum distance, it depends + // on printer technology. I guess the following should work but it crashes. + double dist = 6; // PrintConfig::min_object_distance(config); + if (plater().printer_technology == ptFFF) { + dist = PrintConfig::min_object_distance(plater().config); + } -// coord_t min_obj_distance = scaled(dist); -// auto count = unsigned(m_selected.size()); -// arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); + coord_t min_obj_distance = scaled(dist); + auto count = unsigned(m_selected.polys.size()); + arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); -// try { -// arrangement::arrange(m_selected, m_unselected, min_obj_distance, -// bedshape, -// [this, count](unsigned st) { -// if (st > 0) // will not finalize after last one -// update_status(count - st, arrangestr); -// }, -// [this]() { return was_canceled(); }); -// } catch (std::exception & /*e*/) { -// GUI::show_error(plater().q, -// _(L("Could not arrange model objects! " -// "Some geometries may be invalid."))); -// } + try { + arrangement::arrange(m_selected.polys, m_unselected.polys, + min_obj_distance, + bedshape, + [this, count](unsigned st) { + if (st > 0) // will not finalize after last one + update_status(count - st, arrangestr); + }, + [this]() { return was_canceled(); }); + } catch (std::exception & /*e*/) { + GUI::show_error(plater().q, + _(L("Could not arrange model objects! " + "Some geometries may be invalid."))); + } // finalize just here. update_status(int(count), @@ -2503,11 +2567,27 @@ void find_new_position(const Model & model, coord_t min_d, const arrangement::BedShapeHint &bedhint) { + arrangement::ArrangePolygons movable, fixed; - // TODO + for (const ModelObject *mo : model.objects) + for (const ModelInstance *inst : mo->instances) { + auto it = std::find(instances.begin(), instances.end(), inst); + if (it != instances.end()) + fixed.emplace_back(inst->get_arrange_polygon()); + } + + for (ModelInstance *inst : instances) + movable.emplace_back(inst->get_arrange_polygon()); + + arrangement::arrange(movable, fixed, min_d, bedhint); + + for (size_t i = 0; i < instances.size(); ++i) + if (movable[i].bed_idx == 0) + instances[i]->apply_arrange_result(movable[i].translation, + movable[i].rotation); } -void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() +void Plater::priv::RotoptimizeJob::process() { int obj_idx = plater().get_selected_object_idx(); if (obj_idx < 0) { return; } |