Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authortamasmeszaros <meszaros.q@gmail.com>2020-04-23 19:47:51 +0300
committertamasmeszaros <meszaros.q@gmail.com>2020-04-23 19:47:51 +0300
commit728d90cb334cd78f82c28bc78651cfa545db16a8 (patch)
tree6ae989dea3b84c399c689cba38305cd74a041346 /src
parent1bffc2b99bedb0d0d76baeec52523dc1fef737e1 (diff)
Separate jobs from Plater, re-add big bed workaround
Diffstat (limited to 'src')
-rw-r--r--src/libslic3r/ModelArrange.cpp2
-rw-r--r--src/slic3r/CMakeLists.txt5
-rw-r--r--src/slic3r/GUI/ArrangeJob.cpp223
-rw-r--r--src/slic3r/GUI/ArrangeJob.hpp77
-rw-r--r--src/slic3r/GUI/Job.cpp121
-rw-r--r--src/slic3r/GUI/Job.hpp130
-rw-r--r--src/slic3r/GUI/Plater.cpp504
-rw-r--r--src/slic3r/GUI/Plater.hpp12
-rw-r--r--src/slic3r/GUI/RotoptimizeJob.cpp68
-rw-r--r--src/slic3r/GUI/RotoptimizeJob.hpp24
10 files changed, 634 insertions, 532 deletions
diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp
index 6ddbc5361..85aa25a5f 100644
--- a/src/libslic3r/ModelArrange.cpp
+++ b/src/libslic3r/ModelArrange.cpp
@@ -27,7 +27,7 @@ bool apply_arrange_polys(ArrangePolygons &input, ModelInstancePtrs &instances, V
for(size_t i = 0; i < input.size(); ++i) {
if (input[i].bed_idx != 0) { ret = false; if (vfn) vfn(input[i]); }
if (input[i].bed_idx >= 0)
- instances[i]->apply_arrange_result(input[i].translation,
+ instances[i]->apply_arrange_result(input[i].translation.cast<double>(),
input[i].rotation);
}
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 3047a0b7f..c448fe3ba 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -148,6 +148,11 @@ set(SLIC3R_GUI_SOURCES
GUI/PrintHostDialogs.cpp
GUI/PrintHostDialogs.hpp
GUI/Job.hpp
+ GUI/Job.cpp
+ GUI/ArrangeJob.hpp
+ GUI/ArrangeJob.cpp
+ GUI/RotoptimizeJob.hpp
+ GUI/RotoptimizeJob.cpp
GUI/Mouse3DController.cpp
GUI/Mouse3DController.hpp
GUI/DoubleSlider.cpp
diff --git a/src/slic3r/GUI/ArrangeJob.cpp b/src/slic3r/GUI/ArrangeJob.cpp
new file mode 100644
index 000000000..e43631f7e
--- /dev/null
+++ b/src/slic3r/GUI/ArrangeJob.cpp
@@ -0,0 +1,223 @@
+#include "ArrangeJob.hpp"
+
+#include "libslic3r/MTUtils.hpp"
+
+#include "Plater.hpp"
+#include "GLCanvas3D.hpp"
+#include "GUI.hpp"
+
+namespace Slic3r { namespace GUI {
+
+// Cache the wti info
+class WipeTower: public GLCanvas3D::WipeTowerInfo {
+ using ArrangePolygon = arrangement::ArrangePolygon;
+public:
+ explicit WipeTower(const GLCanvas3D::WipeTowerInfo &wti)
+ : GLCanvas3D::WipeTowerInfo(wti)
+ {}
+
+ explicit WipeTower(GLCanvas3D::WipeTowerInfo &&wti)
+ : GLCanvas3D::WipeTowerInfo(std::move(wti))
+ {}
+
+ void apply_arrange_result(const Vec2d& tr, double rotation)
+ {
+ m_pos = unscaled(tr); m_rotation = rotation;
+ apply_wipe_tower();
+ }
+
+ ArrangePolygon get_arrange_polygon() const
+ {
+ Polygon ap({
+ {coord_t(0), coord_t(0)},
+ {scaled(m_bb_size(X)), coord_t(0)},
+ {scaled(m_bb_size)},
+ {coord_t(0), scaled(m_bb_size(Y))},
+ {coord_t(0), coord_t(0)},
+ });
+
+ ArrangePolygon ret;
+ ret.poly.contour = std::move(ap);
+ ret.translation = scaled(m_pos);
+ ret.rotation = m_rotation;
+ ret.priority++;
+ return ret;
+ }
+};
+
+static WipeTower get_wipe_tower(Plater &plater)
+{
+ return WipeTower{plater.canvas3D()->get_wipe_tower_info()};
+}
+
+void ArrangeJob::clear_input()
+{
+ const Model &model = m_plater->model();
+
+ size_t count = 0, cunprint = 0; // To know how much space to reserve
+ for (auto obj : model.objects)
+ for (auto mi : obj->instances)
+ mi->printable ? count++ : cunprint++;
+
+ m_selected.clear();
+ m_unselected.clear();
+ m_unprintable.clear();
+ m_selected.reserve(count + 1 /* for optional wti */);
+ m_unselected.reserve(count + 1 /* for optional wti */);
+ 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));
+ }
+
+ if (auto wti = get_wipe_tower(*m_plater))
+ m_selected.emplace_back(wti.get_arrange_polygon());
+}
+
+void ArrangeJob::prepare_selected() {
+ clear_input();
+
+ Model &model = m_plater->model();
+ double stride = bed_stride();
+
+ std::vector<const Selection::InstanceIdxsList *>
+ obj_sel(model.objects.size(), nullptr);
+
+ for (auto &s : m_plater->get_selection().get_content())
+ if (s.first < int(obj_sel.size()))
+ obj_sel[size_t(s.first)] = &s.second;
+
+ // Go through the objects and check if inside the selection
+ for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
+ const Selection::InstanceIdxsList * instlist = obj_sel[oidx];
+ ModelObject *mo = model.objects[oidx];
+
+ std::vector<bool> inst_sel(mo->instances.size(), false);
+
+ if (instlist)
+ for (auto inst_id : *instlist)
+ 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]);
+
+ ArrangePolygons &cont = mo->instances[i]->printable ?
+ (inst_sel[i] ? m_selected :
+ m_unselected) :
+ m_unprintable;
+
+ cont.emplace_back(std::move(ap));
+ }
+ }
+
+ if (auto wti = get_wipe_tower(*m_plater)) {
+ ArrangePolygon &&ap = get_arrange_poly(&wti);
+
+ m_plater->get_selection().is_wipe_tower() ?
+ m_selected.emplace_back(std::move(ap)) :
+ m_unselected.emplace_back(std::move(ap));
+ }
+
+ // If the selection was empty arrange everything
+ if (m_selected.empty()) m_selected.swap(m_unselected);
+
+ // The strides have to be removed from the fixed items. For the
+ // arrangeable (selected) items bed_idx is ignored and the
+ // translation is irrelevant.
+ for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride;
+}
+
+void ArrangeJob::prepare()
+{
+ wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all();
+}
+
+void ArrangeJob::process()
+{
+ static const auto arrangestr = _(L("Arranging"));
+
+ double dist = min_object_distance(*m_plater->config());
+
+ arrangement::ArrangeParams params;
+ params.min_obj_distance = scaled(dist);
+
+ auto count = unsigned(m_selected.size() + m_unprintable.size());
+ Points bedpts = get_bed_shape(*m_plater->config());
+
+ params.stopcondition = [this]() { return was_canceled(); };
+
+ try {
+ params.progressind = [this, count](unsigned st) {
+ st += m_unprintable.size();
+ if (st > 0) update_status(int(count - st), arrangestr);
+ };
+
+ arrangement::arrange(m_selected, m_unselected, bedpts, params);
+
+ params.progressind = [this, count](unsigned st) {
+ if (st > 0) update_status(int(count - st), arrangestr);
+ };
+
+ arrangement::arrange(m_unprintable, {}, bedpts, params);
+ } catch (std::exception & /*e*/) {
+ GUI::show_error(m_plater,
+ _(L("Could not arrange model objects! "
+ "Some geometries may be invalid.")));
+ }
+
+ // finalize just here.
+ update_status(int(count),
+ was_canceled() ? _(L("Arranging canceled."))
+ : _(L("Arranging done.")));
+}
+
+void ArrangeJob::finalize() {
+ // Ignore the arrange result if aborted.
+ if (was_canceled()) return;
+
+ // Unprintable items go to the last virtual bed
+ int beds = 0;
+
+ // Apply the arrange result to all selected objects
+ for (ArrangePolygon &ap : m_selected) {
+ beds = std::max(ap.bed_idx, beds);
+ ap.apply();
+ }
+
+ // Get the virtual beds from the unselected items
+ for (ArrangePolygon &ap : m_unselected)
+ beds = std::max(ap.bed_idx, beds);
+
+ // Move the unprintable items to the last virtual bed.
+ for (ArrangePolygon &ap : m_unprintable) {
+ ap.bed_idx += beds + 1;
+ ap.apply();
+ }
+
+ m_plater->update();
+
+ Job::finalize();
+}
+
+arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &plater)
+{
+ return WipeTower{plater.canvas3D()->get_wipe_tower_info()}.get_arrange_polygon();
+}
+
+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);
+}
+
+}} // namespace Slic3r::GUI
diff --git a/src/slic3r/GUI/ArrangeJob.hpp b/src/slic3r/GUI/ArrangeJob.hpp
new file mode 100644
index 000000000..bd097af6b
--- /dev/null
+++ b/src/slic3r/GUI/ArrangeJob.hpp
@@ -0,0 +1,77 @@
+#ifndef ARRANGEJOB_HPP
+#define ARRANGEJOB_HPP
+
+#include "Job.hpp"
+#include "libslic3r/Arrange.hpp"
+
+namespace Slic3r { namespace GUI {
+
+class Plater;
+
+class ArrangeJob : public Job
+{
+ Plater *m_plater;
+
+ 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();
+
+ // Prepare the selected and unselected items separately. If nothing is
+ // selected, behaves as if everything would be selected.
+ void prepare_selected();
+
+protected:
+
+ void prepare() override;
+
+public:
+ ArrangeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
+ : Job{std::move(pri)}, m_plater{plater}
+ {}
+
+ int status_range() const override
+ {
+ return int(m_selected.size() + m_unprintable.size());
+ }
+
+ void process() override;
+
+ void finalize() override;
+};
+
+arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &);
+void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap);
+
+}} // namespace Slic3r::GUI
+
+#endif // ARRANGEJOB_HPP
diff --git a/src/slic3r/GUI/Job.cpp b/src/slic3r/GUI/Job.cpp
new file mode 100644
index 000000000..cc2cb75f1
--- /dev/null
+++ b/src/slic3r/GUI/Job.cpp
@@ -0,0 +1,121 @@
+#include <algorithm>
+
+#include "Job.hpp"
+#include <boost/log/trivial.hpp>
+
+namespace Slic3r {
+
+void GUI::Job::run()
+{
+ m_running.store(true);
+ process();
+ m_running.store(false);
+
+ // ensure to call the last status to finalize the job
+ update_status(status_range(), "");
+}
+
+void GUI::Job::update_status(int st, const wxString &msg)
+{
+ auto evt = new wxThreadEvent();
+ evt->SetInt(st);
+ evt->SetString(msg);
+ wxQueueEvent(this, evt);
+}
+
+GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri)
+ : m_progress(std::move(pri))
+{
+ Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
+ auto msg = evt.GetString();
+ if (!msg.empty())
+ m_progress->set_status_text(msg.ToUTF8().data());
+
+ if (m_finalized) return;
+
+ m_progress->set_progress(evt.GetInt());
+ if (evt.GetInt() == status_range()) {
+ // set back the original range and cancel callback
+ m_progress->set_range(m_range);
+ m_progress->set_cancel_callback();
+ wxEndBusyCursor();
+
+ finalize();
+
+ // dont do finalization again for the same process
+ m_finalized = true;
+ }
+ });
+}
+
+void GUI::Job::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 = m_progress->get_range();
+ m_progress->set_range(status_range());
+
+ // init cancellation flag and set the cancel callback
+ m_canceled.store(false);
+ m_progress->set_cancel_callback(
+ [this]() { m_canceled.store(true); });
+
+ m_finalized = false;
+
+ // Changing cursor to busy
+ wxBeginBusyCursor();
+
+ try { // Execute the job
+ m_thread = create_thread([this] { this->run(); });
+ } 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)
+ }
+}
+
+bool GUI::Job::join(int timeout_ms)
+{
+ if (!m_thread.joinable()) return true;
+
+ if (timeout_ms <= 0)
+ m_thread.join();
+ else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
+ return false;
+
+ return true;
+}
+
+void GUI::ExclusiveJobGroup::start(size_t jid) {
+ assert(jid < m_jobs.size());
+ stop_all();
+ m_jobs[jid]->start();
+}
+
+void GUI::ExclusiveJobGroup::join_all(int wait_ms)
+{
+ std::vector<bool> aborted(m_jobs.size(), false);
+
+ for (size_t jid = 0; jid < m_jobs.size(); ++jid)
+ aborted[jid] = m_jobs[jid]->join(wait_ms);
+
+ if (!std::all_of(aborted.begin(), aborted.end(), [](bool t) { return t; }))
+ BOOST_LOG_TRIVIAL(error) << "Could not abort a job!";
+}
+
+bool GUI::ExclusiveJobGroup::is_any_running() const
+{
+ return std::any_of(m_jobs.begin(), m_jobs.end(),
+ [](const std::unique_ptr<GUI::Job> &j) {
+ return j->is_running();
+ });
+}
+
+}
+
diff --git a/src/slic3r/GUI/Job.hpp b/src/slic3r/GUI/Job.hpp
index ac31b9bdb..b1a652b5e 100644
--- a/src/slic3r/GUI/Job.hpp
+++ b/src/slic3r/GUI/Job.hpp
@@ -31,62 +31,25 @@ class Job : public wxEvtHandler
bool m_finalized = false;
std::shared_ptr<ProgressIndicator> m_progress;
- 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(), "");
- }
+ void run();
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);
- }
-
- bool was_canceled() const { return m_canceled.load(); }
-
+ void update_status(int st, const wxString &msg = "");
+
+ 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() { m_finalized = true; }
-
-
+
public:
- Job(std::shared_ptr<ProgressIndicator> pri) : m_progress(pri)
- {
- Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
- auto msg = evt.GetString();
- if (!msg.empty())
- m_progress->set_status_text(msg.ToUTF8().data());
-
- if (m_finalized) return;
-
- m_progress->set_progress(evt.GetInt());
- if (evt.GetInt() == status_range()) {
- // set back the original range and cancel callback
- m_progress->set_range(m_range);
- m_progress->set_cancel_callback();
- wxEndBusyCursor();
-
- finalize();
-
- // dont do finalization again for the same process
- m_finalized = true;
- }
- });
- }
+ Job(std::shared_ptr<ProgressIndicator> pri);
bool is_finalized() const { return m_finalized; }
@@ -97,59 +60,50 @@ public:
virtual void process() = 0;
- 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 = m_progress->get_range();
- m_progress->set_range(status_range());
-
- // init cancellation flag and set the cancel callback
- m_canceled.store(false);
- m_progress->set_cancel_callback(
- [this]() { m_canceled.store(true); });
-
- m_finalized = false;
-
- // Changing cursor to busy
- wxBeginBusyCursor();
-
- try { // Execute the job
- m_thread = create_thread([this] { this->run(); });
- } 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)
- }
- }
+ void start();
// 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)
- {
- if (!m_thread.joinable()) return true;
-
- if (timeout_ms <= 0)
- m_thread.join();
- else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
- return false;
-
- return true;
- }
+ bool join(int timeout_ms = 0);
bool is_running() const { return m_running.load(); }
void cancel() { m_canceled.store(true); }
};
-}
-}
+// 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.
+class ExclusiveJobGroup
+{
+ static const int ABORT_WAIT_MAX_MS = 10000;
+
+ std::vector<std::unique_ptr<GUI::Job>> m_jobs;
+
+protected:
+ virtual void before_start() {}
+
+public:
+ virtual ~ExclusiveJobGroup() = default;
+
+ size_t add_job(std::unique_ptr<GUI::Job> &&job)
+ {
+ m_jobs.emplace_back(std::move(job));
+ return m_jobs.size() - 1;
+ }
+
+ void start(size_t jid);
+
+ void cancel_all() { for (auto& j : m_jobs) j->cancel(); }
+
+ void join_all(int wait_ms = 0);
+
+ void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); }
+
+ bool is_any_running() const;
+};
+
+}} // namespace Slic3r::GUI
#endif // JOB_HPP
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 55772f456..2500ee6b8 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -36,7 +36,6 @@
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/SLA/Hollowing.hpp"
-#include "libslic3r/SLA/Rotfinder.hpp"
#include "libslic3r/SLA/SupportPoint.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Print.hpp"
@@ -44,13 +43,6 @@
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Utils.hpp"
-//#include "libslic3r/ClipperUtils.hpp"
-
-// #include "libnest2d/optimizers/nlopt/genetic.hpp"
-// #include "libnest2d/backends/clipper/geometries.hpp"
-// #include "libnest2d/utils/rotcalipers.hpp"
-#include "libslic3r/MinAreaBoundingBox.hpp"
-
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
@@ -69,7 +61,8 @@
#include "Camera.hpp"
#include "Mouse3DController.hpp"
#include "Tab.hpp"
-#include "Job.hpp"
+#include "ArrangeJob.hpp"
+#include "RotoptimizeJob.hpp"
#include "PresetBundle.hpp"
#include "BackgroundSlicingProcess.hpp"
#include "ProgressStatusBar.hpp"
@@ -1485,311 +1478,37 @@ struct Plater::priv
BackgroundSlicingProcess background_process;
bool suppressed_backround_processing_update { false };
- // Cache the wti info
- class WipeTower: public GLCanvas3D::WipeTowerInfo {
- using ArrangePolygon = arrangement::ArrangePolygon;
- friend priv;
- public:
-
- void apply_arrange_result(const Vec2d& tr, double rotation)
- {
- m_pos = unscaled(tr); m_rotation = rotation;
- apply_wipe_tower();
- }
-
- ArrangePolygon get_arrange_polygon() const
- {
- Polygon p({
- {coord_t(0), coord_t(0)},
- {scaled(m_bb_size(X)), coord_t(0)},
- {scaled(m_bb_size)},
- {coord_t(0), scaled(m_bb_size(Y))},
- {coord_t(0), coord_t(0)},
- });
-
- ArrangePolygon ret;
- ret.poly.contour = std::move(p);
- ret.translation = scaled(m_pos);
- ret.rotation = m_rotation;
- ret.priority++;
- return ret;
- }
- } wipetower;
-
- WipeTower& updated_wipe_tower() {
- auto wti = view3D->get_canvas3d()->get_wipe_tower_info();
- wipetower.m_pos = wti.pos();
- wipetower.m_rotation = wti.rotation();
- wipetower.m_bb_size = wti.bb_size();
- return wipetower;
- }
-
- // A class to handle UI jobs like arranging and optimizing rotation.
- // These are not instant jobs, the user has to be informed about their
- // state in the status progress indicator. On the other hand they are
- // separated from the background slicing process. Ideally, these jobs should
- // run when the background process is not running.
- //
- // TODO: A mechanism would be useful for blocking the plater interactions:
- // 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 PlaterJob: public Job
- {
- priv *m_plater;
- protected:
-
- priv & plater() { return *m_plater; }
- const priv &plater() const { return *m_plater; }
-
- // Launched when the job is finished. It refreshes the 3Dscene by def.
- void finalize() override
- {
- // Do a full refresh of scene tree, including regenerating
- // all the GLVolumes. FIXME The update function shall just
- // reload the modified matrices.
- if (!Job::was_canceled())
- plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
-
- Job::finalize();
- }
-
- public:
- PlaterJob(priv *_plater)
- : Job(_plater->statusbar()), m_plater(_plater)
- {}
- };
-
- enum class Jobs : size_t {
- Arrange,
- Rotoptimize
- };
-
- class ArrangeJob : public PlaterJob
- {
- 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() {
- const Model &model = plater().model;
-
- size_t count = 0, cunprint = 0; // To know how much space to reserve
- for (auto obj : model.objects)
- for (auto mi : obj->instances)
- mi->printable ? count++ : cunprint++;
-
- m_selected.clear();
- m_unselected.clear();
- m_unprintable.clear();
- m_selected.reserve(count + 1 /* for optional wti */);
- m_unselected.reserve(count + 1 /* for optional wti */);
- m_unprintable.reserve(cunprint /* for optional wti */);
- }
-
- // Stride between logical beds
- double bed_stride() const {
- double bedwidth = plater().bed_shape_bb().size().x();
- return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
- }
-
- // 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() {
- clear_input();
-
- for (ModelObject *obj: plater().model.objects)
- for (ModelInstance *mi : obj->instances) {
- ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
- cont.emplace_back(get_arrange_poly(mi));
- }
-
- auto& wti = plater().updated_wipe_tower();
- if (wti) m_selected.emplace_back(get_arrange_poly(&wti));
- }
-
- // Prepare the selected and unselected items separately. If nothing is
- // selected, behaves as if everything would be selected.
- void prepare_selected() {
- clear_input();
-
- Model &model = plater().model;
- coord_t stride = bed_stride();
-
- std::vector<const Selection::InstanceIdxsList *>
- obj_sel(model.objects.size(), nullptr);
-
- for (auto &s : plater().get_selection().get_content())
- if (s.first < int(obj_sel.size()))
- obj_sel[size_t(s.first)] = &s.second;
-
- // Go through the objects and check if inside the selection
- for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
- const Selection::InstanceIdxsList * instlist = obj_sel[oidx];
- ModelObject *mo = model.objects[oidx];
-
- std::vector<bool> inst_sel(mo->instances.size(), false);
-
- if (instlist)
- for (auto inst_id : *instlist)
- 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]);
-
- ArrangePolygons &cont = mo->instances[i]->printable ?
- (inst_sel[i] ? m_selected :
- m_unselected) :
- m_unprintable;
-
- cont.emplace_back(std::move(ap));
- }
- }
-
- auto& wti = plater().updated_wipe_tower();
- if (wti) {
- ArrangePolygon &&ap = get_arrange_poly(&wti);
-
- plater().get_selection().is_wipe_tower() ?
- m_selected.emplace_back(std::move(ap)) :
- m_unselected.emplace_back(std::move(ap));
- }
-
- // If the selection was empty arrange everything
- if (m_selected.empty()) m_selected.swap(m_unselected);
-
- // The strides have to be removed from the fixed items. For the
- // arrangeable (selected) items bed_idx is ignored and the
- // translation is irrelevant.
- for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride;
- }
-
- protected:
-
- void prepare() override
- {
- wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all();
- }
-
- public:
- using PlaterJob::PlaterJob;
-
- int status_range() const override
- {
- return int(m_selected.size() + m_unprintable.size());
- }
-
- void process() override;
-
- void finalize() override {
- // Ignore the arrange result if aborted.
- if (was_canceled()) return;
-
- // Unprintable items go to the last virtual bed
- int beds = 0;
-
- // Apply the arrange result to all selected objects
- for (ArrangePolygon &ap : m_selected) {
- beds = std::max(ap.bed_idx, beds);
- ap.apply();
- }
-
- // Get the virtual beds from the unselected items
- for (ArrangePolygon &ap : m_unselected)
- beds = std::max(ap.bed_idx, beds);
-
- // Move the unprintable items to the last virtual bed.
- for (ArrangePolygon &ap : m_unprintable) {
- ap.bed_idx += beds + 1;
- ap.apply();
- }
-
- plater().update();
- }
- };
-
- class RotoptimizeJob : public PlaterJob
- {
- public:
- using PlaterJob::PlaterJob;
- 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.
- class ExclusiveJobGroup {
-
- static const int ABORT_WAIT_MAX_MS = 10000;
-
- priv * 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
- // Register the instance of the class in the m_jobs container
- // if it cannot run concurrently with other jobs in this group
-
- std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job,
- rotoptimize_job};
-
+ // started. It is up the the plater to ensure that the background slicing
+ // can't be restarted while a ui job is still running.
+ class Jobs: public ExclusiveJobGroup
+ {
+ priv *m;
+ size_t m_arrange_id, m_rotoptimize_id;
+
+ void before_start() override { m->background_process.stop(); }
+
public:
- ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {}
-
- void start(Jobs jid) {
- m_plater->background_process.stop();
- stop_all();
- m_jobs[size_t(jid)].get().start();
+ Jobs(priv *_m) : m(_m)
+ {
+ m_arrange_id = add_job(std::make_unique<ArrangeJob>(m->statusbar(), m->q));
+ m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m->statusbar(), m->q));
}
-
- void cancel_all() { for (Job& j : m_jobs) j.cancel(); }
-
- void join_all(int wait_ms = 0)
+
+ void arrange()
{
- std::vector<bool> aborted(m_jobs.size(), false);
-
- for (size_t jid = 0; jid < m_jobs.size(); ++jid)
- aborted[jid] = m_jobs[jid].get().join(wait_ms);
-
- if (!all_of(aborted))
- BOOST_LOG_TRIVIAL(error) << "Could not abort a job!";
+ m->take_snapshot(_(L("Arrange")));
+ start(m_arrange_id);
}
-
- void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); }
-
- const Job& get(Jobs jobid) const { return m_jobs[size_t(jobid)]; }
-
- bool is_any_running() const
+
+ void optimize_rotation()
{
- return std::any_of(m_jobs.begin(),
- m_jobs.end(),
- [](const Job &j) { return j.is_running(); });
+ m->take_snapshot(_(L("Optimize Rotation")));
+ start(m_rotoptimize_id);
}
-
- } m_ui_jobs{this};
+
+ } m_ui_jobs;
bool delayed_scene_refresh;
std::string delayed_error_message;
@@ -1808,10 +1527,10 @@ struct Plater::priv
priv(Plater *q, MainFrame *main_frame);
~priv();
- enum class UpdateParams {
- FORCE_FULL_SCREEN_REFRESH = 1,
- FORCE_BACKGROUND_PROCESSING_UPDATE = 2,
- POSTPONE_VALIDATION_ERROR_MESSAGE = 4,
+ enum class UpdateParams {
+ FORCE_FULL_SCREEN_REFRESH = 1,
+ FORCE_BACKGROUND_PROCESSING_UPDATE = 2,
+ POSTPONE_VALIDATION_ERROR_MESSAGE = 4,
};
void update(unsigned int flags = 0);
void select_view(const std::string& direction);
@@ -1847,9 +1566,7 @@ struct Plater::priv
std::string get_config(const std::string &key) const;
BoundingBoxf bed_shape_bb() const;
BoundingBox scaled_bed_shape_bb() const;
- arrangement::BedShapeHint get_bed_shape_hint() const;
- void find_new_position(const ModelInstancePtrs &instances, coord_t min_d);
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config);
std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects);
wxString get_export_file(GUI::FileType file_type);
@@ -1867,8 +1584,6 @@ struct Plater::priv
void delete_object_from_model(size_t obj_idx);
void reset();
void mirror(Axis axis);
- void arrange();
- void sla_optimize_rotation();
void split_object();
void split_volume();
void scale_selection_to_fit_print_volume();
@@ -2035,6 +1750,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
"support_material", "support_material_extruder", "support_material_interface_extruder", "support_material_contact_distance", "raft_layers"
}))
, sidebar(new Sidebar(q))
+ , m_ui_jobs(this)
, delayed_scene_refresh(false)
, view_toolbar(GLToolbar::Radio, "View")
, m_project_filename(wxEmptyString)
@@ -2110,14 +1826,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
sidebar->Bind(EVT_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); });
wxGLCanvas* view3D_canvas = view3D->get_wxglcanvas();
+
// 3DScene events:
view3D_canvas->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); });
view3D_canvas->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this);
view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this);
view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); });
- view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); });
+ view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); });
view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); });
- view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
+ view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int> &evt)
{ if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); });
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); });
@@ -2142,7 +1859,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); });
- view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { arrange(); });
+ view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); });
@@ -2810,40 +2527,12 @@ void Plater::priv::mirror(Axis axis)
view3D->mirror_selection(axis);
}
-void Plater::priv::arrange()
-{
- this->take_snapshot(_L("Arrange"));
- m_ui_jobs.start(Jobs::Arrange);
-}
-
-
-// This method will find an optimal orientation for the currently selected item
-// Very similar in nature to the arrange method above...
-void Plater::priv::sla_optimize_rotation() {
- this->take_snapshot(_L("Optimize Rotation"));
- m_ui_jobs.start(Jobs::Rotoptimize);
-}
-
-arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const {
-
- const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
- assert(bed_shape_opt);
-
- if (!bed_shape_opt) return {};
-
- 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::find_new_position(const ModelInstancePtrs &instances,
+void Plater::find_new_position(const ModelInstancePtrs &instances,
coord_t min_d)
{
arrangement::ArrangePolygons movable, fixed;
-
- for (const ModelObject *mo : model.objects)
+
+ for (const ModelObject *mo : p->model.objects)
for (const ModelInstance *inst : mo->instances) {
auto it = std::find(instances.begin(), instances.end(), inst);
auto arrpoly = inst->get_arrange_polygon();
@@ -2853,11 +2542,12 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances,
else
movable.emplace_back(std::move(arrpoly));
}
-
- if (updated_wipe_tower())
- fixed.emplace_back(wipetower.get_arrange_polygon());
-
- arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint());
+
+ if (p->view3D->get_canvas3d()->get_wipe_tower_info())
+ fixed.emplace_back(get_wipe_tower_arrangepoly(*this));
+
+ arrangement::arrange(movable, fixed, get_bed_shape(*config()),
+ arrangement::ArrangeParams{min_d});
for (size_t i = 0; i < instances.size(); ++i)
if (movable[i].bed_idx == 0)
@@ -2865,90 +2555,6 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances,
movable[i].rotation);
}
-void Plater::priv::ArrangeJob::process() {
- static const auto arrangestr = _L("Arranging");
-
- double dist = min_object_distance(*plater().config);
-
- coord_t min_d = scaled(dist);
- auto count = unsigned(m_selected.size() + m_unprintable.size());
- arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint();
-
- auto stopfn = [this]() { return was_canceled(); };
-
- try {
- arrangement::arrange(m_selected, m_unselected, min_d, bedshape,
- [this, count](unsigned st) {
- st += m_unprintable.size();
- if (st > 0) update_status(int(count - st), arrangestr);
- }, stopfn);
- arrangement::arrange(m_unprintable, {}, min_d, bedshape,
- [this, count](unsigned st) {
- if (st > 0) update_status(int(count - st), arrangestr);
- }, stopfn);
- } 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),
- was_canceled() ? _L("Arranging canceled.")
- : _L("Arranging done."));
-}
-
-void Plater::priv::RotoptimizeJob::process()
-{
- int obj_idx = plater().get_selected_object_idx();
- if (obj_idx < 0) { return; }
-
- ModelObject *o = plater().model.objects[size_t(obj_idx)];
-
- auto r = sla::find_best_rotation(
- *o,
- .005f,
- [this](unsigned s) {
- if (s < 100)
- update_status(int(s),
- _L("Searching for optimal orientation"));
- },
- [this]() { return was_canceled(); });
-
-
- double mindist = 6.0; // FIXME
-
- if (!was_canceled()) {
- for(ModelInstance * oi : o->instances) {
- oi->set_rotation({r[X], r[Y], r[Z]});
-
- auto trmatrix = oi->get_transformation().get_matrix();
- Polygon trchull = o->convex_hull_2d(trmatrix);
-
- MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
- double r = rotbb.angle_to_X();
-
- // The box should be landscape
- if(rotbb.width() < rotbb.height()) r += PI / 2;
-
- Vec3d rt = oi->get_rotation(); rt(Z) += r;
-
- oi->set_rotation(rt);
- }
-
- plater().find_new_position(o->instances, scaled(mindist));
-
- // Correct the z offset of the object which was corrupted be
- // the rotation
- o->ensure_on_bed();
- }
-
- update_status(100,
- was_canceled() ? _L("Orientation search canceled.")
- : _L("Orientation found."));
-}
-
-
void Plater::priv::split_object()
{
int obj_idx = get_selected_object_idx();
@@ -3589,7 +3195,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
}
// update plater with new config
- wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config());
+ q->on_config_change(wxGetApp().preset_bundle->full_config());
/* Settings list can be changed after printer preset changing, so
* update all settings items for all item had it.
* Furthermore, Layers editing is implemented only for FFF printers
@@ -4036,8 +3642,12 @@ bool Plater::priv::complit_init_sla_object_menu()
sla_object_menu.AppendSeparator();
// Add the automatic rotation sub-menu
- append_menu_item(&sla_object_menu, wxID_ANY, _L("Optimize orientation"), _L("Optimize the rotation of the object for better print results."),
- [this](wxCommandEvent&) { sla_optimize_rotation(); });
+ append_menu_item(
+ &sla_object_menu, wxID_ANY, _(L("Optimize orientation")),
+ _(L("Optimize the rotation of the object for better print results.")),
+ [this](wxCommandEvent &) {
+ m_ui_jobs.optimize_rotation();
+ });
return true;
}
@@ -4733,7 +4343,7 @@ void Plater::increase_instances(size_t num)
sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num);
if (p->get_config("autocenter") == "1")
- p->arrange();
+ arrange();
p->update();
@@ -5467,6 +5077,11 @@ bool Plater::is_export_gcode_scheduled() const
return p->background_process.is_export_scheduled();
}
+const Selection &Plater::get_selection() const
+{
+ return p->get_selection();
+}
+
int Plater::get_selected_object_idx()
{
return p->get_selected_object_idx();
@@ -5492,6 +5107,11 @@ BoundingBoxf Plater::bed_shape_bb() const
return p->bed_shape_bb();
}
+void Plater::arrange()
+{
+ p->m_ui_jobs.arrange();
+}
+
void Plater::set_current_canvas_as_dirty()
{
p->set_current_canvas_as_dirty();
@@ -5514,6 +5134,8 @@ PrinterTechnology Plater::printer_technology() const
return p->printer_technology;
}
+const DynamicPrintConfig * Plater::config() const { return p->config; }
+
void Plater::set_printer_technology(PrinterTechnology printer_technology)
{
p->printer_technology = printer_technology;
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index efdaa75cc..121689148 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -9,8 +9,10 @@
#include <wx/bmpcbox.h>
#include "Preset.hpp"
+#include "Selection.hpp"
#include "libslic3r/BoundingBox.hpp"
+#include "Job.hpp"
#include "wxExtensions.hpp"
class wxButton;
@@ -252,12 +254,16 @@ public:
void set_project_filename(const wxString& filename);
bool is_export_gcode_scheduled() const;
-
+
+ const Selection& get_selection() const;
int get_selected_object_idx();
bool is_single_full_object_selection() const;
GLCanvas3D* canvas3D();
GLCanvas3D* get_current_canvas3D();
BoundingBoxf bed_shape_bb() const;
+
+ void arrange();
+ void find_new_position(const ModelInstancePtrs &instances, coord_t min_d);
void set_current_canvas_as_dirty();
#if ENABLE_NON_STATIC_CANVAS_MANAGER
@@ -266,6 +272,7 @@ public:
#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
PrinterTechnology printer_technology() const;
+ const DynamicPrintConfig * config() const;
void set_printer_technology(PrinterTechnology printer_technology);
void copy_selection_to_clipboard();
@@ -371,6 +378,7 @@ private:
bool m_was_scheduled;
};
-}}
+} // namespace GUI
+} // namespace Slic3r
#endif
diff --git a/src/slic3r/GUI/RotoptimizeJob.cpp b/src/slic3r/GUI/RotoptimizeJob.cpp
new file mode 100644
index 000000000..10f95201b
--- /dev/null
+++ b/src/slic3r/GUI/RotoptimizeJob.cpp
@@ -0,0 +1,68 @@
+#include "RotoptimizeJob.hpp"
+
+#include "libslic3r/MTUtils.hpp"
+#include "libslic3r/SLA/Rotfinder.hpp"
+#include "libslic3r/MinAreaBoundingBox.hpp"
+
+#include "Plater.hpp"
+
+namespace Slic3r { namespace GUI {
+
+void RotoptimizeJob::process()
+{
+ int obj_idx = m_plater->get_selected_object_idx();
+ if (obj_idx < 0) { return; }
+
+ ModelObject *o = m_plater->model().objects[size_t(obj_idx)];
+
+ auto r = sla::find_best_rotation(
+ *o,
+ .005f,
+ [this](unsigned s) {
+ if (s < 100)
+ update_status(int(s),
+ _(L("Searching for optimal orientation")));
+ },
+ [this]() { return was_canceled(); });
+
+
+ double mindist = 6.0; // FIXME
+
+ if (!was_canceled()) {
+ for(ModelInstance * oi : o->instances) {
+ oi->set_rotation({r[X], r[Y], r[Z]});
+
+ auto trmatrix = oi->get_transformation().get_matrix();
+ Polygon trchull = o->convex_hull_2d(trmatrix);
+
+ MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
+ double phi = rotbb.angle_to_X();
+
+ // The box should be landscape
+ if(rotbb.width() < rotbb.height()) phi += PI / 2;
+
+ Vec3d rt = oi->get_rotation(); rt(Z) += phi;
+
+ oi->set_rotation(rt);
+ }
+
+ m_plater->find_new_position(o->instances, scaled(mindist));
+
+ // Correct the z offset of the object which was corrupted be
+ // the rotation
+ o->ensure_on_bed();
+ }
+
+ update_status(100, was_canceled() ? _(L("Orientation search canceled.")) :
+ _(L("Orientation found.")));
+}
+
+void RotoptimizeJob::finalize()
+{
+ if (!was_canceled())
+ m_plater->update();
+
+ Job::finalize();
+}
+
+}}
diff --git a/src/slic3r/GUI/RotoptimizeJob.hpp b/src/slic3r/GUI/RotoptimizeJob.hpp
new file mode 100644
index 000000000..983c43c68
--- /dev/null
+++ b/src/slic3r/GUI/RotoptimizeJob.hpp
@@ -0,0 +1,24 @@
+#ifndef ROTOPTIMIZEJOB_HPP
+#define ROTOPTIMIZEJOB_HPP
+
+#include "Job.hpp"
+
+namespace Slic3r { namespace GUI {
+
+class Plater;
+
+class RotoptimizeJob : public Job
+{
+ Plater *m_plater;
+public:
+ RotoptimizeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
+ : Job{std::move(pri)}, m_plater{plater}
+ {}
+
+ void process() override;
+ void finalize() override;
+};
+
+}} // namespace Slic3r::GUI
+
+#endif // ROTOPTIMIZEJOB_HPP