Merge branch 'tm_ui_job_rework_3' into dev
This commit is contained in:
commit
cbcda3b0b5
@ -169,9 +169,11 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/PrintHostDialogs.cpp
|
||||
GUI/PrintHostDialogs.hpp
|
||||
GUI/Jobs/Job.hpp
|
||||
GUI/Jobs/Job.cpp
|
||||
GUI/Jobs/PlaterJob.hpp
|
||||
GUI/Jobs/PlaterJob.cpp
|
||||
GUI/Jobs/Worker.hpp
|
||||
GUI/Jobs/BoostThreadWorker.hpp
|
||||
GUI/Jobs/BoostThreadWorker.cpp
|
||||
GUI/Jobs/BusyCursorJob.hpp
|
||||
GUI/Jobs/PlaterWorker.hpp
|
||||
GUI/Jobs/ArrangeJob.hpp
|
||||
GUI/Jobs/ArrangeJob.cpp
|
||||
GUI/Jobs/RotoptimizeJob.hpp
|
||||
@ -183,6 +185,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Jobs/ProgressIndicator.hpp
|
||||
GUI/Jobs/NotificationProgressIndicator.hpp
|
||||
GUI/Jobs/NotificationProgressIndicator.cpp
|
||||
GUI/Jobs/ThreadSafeQueue.hpp
|
||||
GUI/Jobs/SLAImportDialog.hpp
|
||||
GUI/ProgressStatusBar.hpp
|
||||
GUI/ProgressStatusBar.cpp
|
||||
GUI/Mouse3DController.cpp
|
||||
|
@ -9,6 +9,8 @@
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/GUI/MainFrame.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
|
||||
// To show a message box if GUI initialization ends up with an exception thrown.
|
||||
#include <wx/msgdlg.h>
|
||||
|
@ -540,11 +540,12 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui,
|
||||
ImVec2 button_sz = {btn_txt_sz.x + padding.x, btn_txt_sz.y + padding.y};
|
||||
ImGui::SetCursorPosX(padding.x + sz.x - button_sz.x);
|
||||
|
||||
if (wxGetApp().plater()->is_any_job_running())
|
||||
if (!wxGetApp().plater()->get_ui_job_worker().is_idle())
|
||||
imgui->disabled_begin(true);
|
||||
|
||||
if ( imgui->button(btn_txt) ) {
|
||||
wxGetApp().plater()->optimize_rotation();
|
||||
replace_job(wxGetApp().plater()->get_ui_job_worker(),
|
||||
std::make_unique<RotoptimizeJob>());
|
||||
}
|
||||
|
||||
imgui->disabled_end();
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define slic3r_GLGizmoRotate_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "../Jobs/RotoptimizeJob.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
@ -162,49 +162,43 @@ void ArrangeJob::prepare()
|
||||
wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all();
|
||||
}
|
||||
|
||||
void ArrangeJob::on_exception(const std::exception_ptr &eptr)
|
||||
void ArrangeJob::process(Ctl &ctl)
|
||||
{
|
||||
try {
|
||||
if (eptr)
|
||||
std::rethrow_exception(eptr);
|
||||
} catch (libnest2d::GeometryException &) {
|
||||
show_error(m_plater, _(L("Could not arrange model objects! "
|
||||
"Some geometries may be invalid.")));
|
||||
} catch (std::exception &) {
|
||||
PlaterJob::on_exception(eptr);
|
||||
}
|
||||
}
|
||||
static const auto arrangestr = _u8L("Arranging");
|
||||
|
||||
void ArrangeJob::process()
|
||||
{
|
||||
static const auto arrangestr = _(L("Arranging"));
|
||||
ctl.update_status(0, arrangestr);
|
||||
ctl.call_on_main_thread([this]{ prepare(); }).wait();;
|
||||
|
||||
arrangement::ArrangeParams params = get_arrange_params(m_plater);
|
||||
|
||||
auto count = unsigned(m_selected.size() + m_unprintable.size());
|
||||
auto count = unsigned(m_selected.size() + m_unprintable.size());
|
||||
Points bedpts = get_bed_shape(*m_plater->config());
|
||||
|
||||
params.stopcondition = [this]() { return was_canceled(); };
|
||||
|
||||
params.progressind = [this, count](unsigned st) {
|
||||
|
||||
params.stopcondition = [&ctl]() { return ctl.was_canceled(); };
|
||||
|
||||
params.progressind = [this, count, &ctl](unsigned st) {
|
||||
st += m_unprintable.size();
|
||||
if (st > 0) update_status(int(count - st), arrangestr);
|
||||
if (st > 0) ctl.update_status(int(count - st) * 100 / status_range(), arrangestr);
|
||||
};
|
||||
|
||||
ctl.update_status(0, arrangestr);
|
||||
|
||||
arrangement::arrange(m_selected, m_unselected, bedpts, params);
|
||||
|
||||
params.progressind = [this, count](unsigned st) {
|
||||
if (st > 0) update_status(int(count - st), arrangestr);
|
||||
params.progressind = [this, count, &ctl](unsigned st) {
|
||||
if (st > 0) ctl.update_status(int(count - st) * 100 / status_range(), arrangestr);
|
||||
};
|
||||
|
||||
arrangement::arrange(m_unprintable, {}, bedpts, params);
|
||||
|
||||
// finalize just here.
|
||||
update_status(int(count),
|
||||
was_canceled() ? _(L("Arranging canceled."))
|
||||
: _(L("Arranging done.")));
|
||||
ctl.update_status(int(count) * 100 / status_range(), ctl.was_canceled() ?
|
||||
_u8L("Arranging canceled.") :
|
||||
_u8L("Arranging done."));
|
||||
}
|
||||
|
||||
ArrangeJob::ArrangeJob() : m_plater{wxGetApp().plater()} {}
|
||||
|
||||
static std::string concat_strings(const std::set<std::string> &strings,
|
||||
const std::string &delim = "\n")
|
||||
{
|
||||
@ -215,10 +209,21 @@ static std::string concat_strings(const std::set<std::string> &strings,
|
||||
});
|
||||
}
|
||||
|
||||
void ArrangeJob::finalize() {
|
||||
// Ignore the arrange result if aborted.
|
||||
if (was_canceled()) return;
|
||||
|
||||
void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) {
|
||||
try {
|
||||
if (eptr)
|
||||
std::rethrow_exception(eptr);
|
||||
} catch (libnest2d::GeometryException &) {
|
||||
show_error(m_plater, _(L("Could not arrange model objects! "
|
||||
"Some geometries may be invalid.")));
|
||||
eptr = nullptr;
|
||||
} catch(...) {
|
||||
eptr = std::current_exception();
|
||||
}
|
||||
|
||||
if (canceled || eptr)
|
||||
return;
|
||||
|
||||
// Unprintable items go to the last virtual bed
|
||||
int beds = 0;
|
||||
|
||||
@ -250,8 +255,6 @@ void ArrangeJob::finalize() {
|
||||
_L("Arrangement ignored the following objects which can't fit into a single bed:\n%s"),
|
||||
concat_strings(names, "\n")));
|
||||
}
|
||||
|
||||
Job::finalize();
|
||||
}
|
||||
|
||||
std::optional<arrangement::ArrangePolygon>
|
||||
|
@ -1,7 +1,9 @@
|
||||
#ifndef ARRANGEJOB_HPP
|
||||
#define ARRANGEJOB_HPP
|
||||
|
||||
#include "PlaterJob.hpp"
|
||||
#include <optional>
|
||||
|
||||
#include "Job.hpp"
|
||||
#include "libslic3r/Arrange.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
@ -10,13 +12,16 @@ class ModelInstance;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class ArrangeJob : public PlaterJob
|
||||
class Plater;
|
||||
|
||||
class ArrangeJob : public Job
|
||||
{
|
||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||
using ArrangePolygons = arrangement::ArrangePolygons;
|
||||
|
||||
ArrangePolygons m_selected, m_unselected, m_unprintable;
|
||||
std::vector<ModelInstance*> m_unarranged;
|
||||
Plater *m_plater;
|
||||
|
||||
// clear m_selected and m_unselected, reserve space for next usage
|
||||
void clear_input();
|
||||
@ -30,25 +35,20 @@ class ArrangeJob : public PlaterJob
|
||||
|
||||
ArrangePolygon get_arrange_poly_(ModelInstance *mi);
|
||||
|
||||
protected:
|
||||
|
||||
void prepare() override;
|
||||
|
||||
void on_exception(const std::exception_ptr &) override;
|
||||
|
||||
void process() override;
|
||||
|
||||
public:
|
||||
ArrangeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
: PlaterJob{std::move(pri), plater}
|
||||
{}
|
||||
|
||||
int status_range() const override
|
||||
void prepare();
|
||||
|
||||
void process(Ctl &ctl) override;
|
||||
|
||||
ArrangeJob();
|
||||
|
||||
int status_range() const
|
||||
{
|
||||
return int(m_selected.size() + m_unprintable.size());
|
||||
}
|
||||
|
||||
void finalize() override;
|
||||
void finalize(bool canceled, std::exception_ptr &e) override;
|
||||
};
|
||||
|
||||
std::optional<arrangement::ArrangePolygon> get_wipe_tower_arrangepoly(const Plater &);
|
||||
|
181
src/slic3r/GUI/Jobs/BoostThreadWorker.cpp
Normal file
181
src/slic3r/GUI/Jobs/BoostThreadWorker.cpp
Normal file
@ -0,0 +1,181 @@
|
||||
#include <exception>
|
||||
|
||||
#include "BoostThreadWorker.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
void BoostThreadWorker::WorkerMessage::deliver(BoostThreadWorker &runner)
|
||||
{
|
||||
switch(MsgType(get_type())) {
|
||||
case Empty: break;
|
||||
case Status: {
|
||||
auto info = boost::get<StatusInfo>(m_data);
|
||||
if (runner.get_pri()) {
|
||||
runner.get_pri()->set_progress(info.status);
|
||||
runner.get_pri()->set_status_text(info.msg.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Finalize: {
|
||||
auto& entry = boost::get<JobEntry>(m_data);
|
||||
entry.job->finalize(entry.canceled, entry.eptr);
|
||||
|
||||
// Unhandled exceptions are rethrown without mercy.
|
||||
if (entry.eptr)
|
||||
std::rethrow_exception(entry.eptr);
|
||||
|
||||
break;
|
||||
}
|
||||
case MainThreadCall: {
|
||||
auto &calldata = boost::get<MainThreadCallData >(m_data);
|
||||
calldata.fn();
|
||||
calldata.promise.set_value();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BoostThreadWorker::run()
|
||||
{
|
||||
bool stop = false;
|
||||
while (!stop) {
|
||||
m_input_queue
|
||||
.consume_one(BlockingWait{0, &m_running}, [this, &stop](JobEntry &e) {
|
||||
if (!e.job)
|
||||
stop = true;
|
||||
else {
|
||||
m_canceled.store(false);
|
||||
|
||||
try {
|
||||
e.job->process(*this);
|
||||
} catch (...) {
|
||||
e.eptr = std::current_exception();
|
||||
}
|
||||
|
||||
e.canceled = m_canceled.load();
|
||||
m_output_queue.push(std::move(e)); // finalization message
|
||||
}
|
||||
m_running.store(false);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
void BoostThreadWorker::update_status(int st, const std::string &msg)
|
||||
{
|
||||
m_output_queue.push(st, msg);
|
||||
}
|
||||
|
||||
std::future<void> BoostThreadWorker::call_on_main_thread(std::function<void ()> fn)
|
||||
{
|
||||
MainThreadCallData cbdata{std::move(fn), {}};
|
||||
std::future<void> future = cbdata.promise.get_future();
|
||||
|
||||
m_output_queue.push(std::move(cbdata));
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
BoostThreadWorker::BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
||||
boost::thread::attributes &attribs,
|
||||
const char * name)
|
||||
: m_progress(std::move(pri)), m_name{name}
|
||||
{
|
||||
if (m_progress)
|
||||
m_progress->set_cancel_callback([this](){ cancel(); });
|
||||
|
||||
m_thread = create_thread(attribs, [this] { this->run(); });
|
||||
|
||||
std::string nm{name};
|
||||
if (!nm.empty()) set_thread_name(m_thread, name);
|
||||
}
|
||||
|
||||
constexpr int ABORT_WAIT_MAX_MS = 10000;
|
||||
|
||||
BoostThreadWorker::~BoostThreadWorker()
|
||||
{
|
||||
bool joined = false;
|
||||
try {
|
||||
cancel_all();
|
||||
wait_for_idle(ABORT_WAIT_MAX_MS);
|
||||
m_input_queue.push(JobEntry{nullptr});
|
||||
joined = join(ABORT_WAIT_MAX_MS);
|
||||
} catch(...) {}
|
||||
|
||||
if (!joined)
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
<< "Could not join worker thread '" << m_name << "'";
|
||||
}
|
||||
|
||||
bool BoostThreadWorker::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 true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BoostThreadWorker::process_events()
|
||||
{
|
||||
while (m_output_queue.consume_one([this](WorkerMessage &msg) {
|
||||
msg.deliver(*this);
|
||||
}));
|
||||
}
|
||||
|
||||
bool BoostThreadWorker::wait_for_current_job(unsigned timeout_ms)
|
||||
{
|
||||
bool ret = true;
|
||||
|
||||
if (!is_idle()) {
|
||||
bool was_finish = false;
|
||||
bool timeout_reached = false;
|
||||
while (!timeout_reached && !was_finish) {
|
||||
timeout_reached =
|
||||
!m_output_queue.consume_one(BlockingWait{timeout_ms},
|
||||
[this, &was_finish](
|
||||
WorkerMessage &msg) {
|
||||
msg.deliver(*this);
|
||||
if (msg.get_type() ==
|
||||
WorkerMessage::Finalize)
|
||||
was_finish = true;
|
||||
});
|
||||
}
|
||||
|
||||
ret = !timeout_reached;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool BoostThreadWorker::wait_for_idle(unsigned timeout_ms)
|
||||
{
|
||||
bool timeout_reached = false;
|
||||
while (!timeout_reached && !is_idle()) {
|
||||
timeout_reached = !m_output_queue
|
||||
.consume_one(BlockingWait{timeout_ms},
|
||||
[this](WorkerMessage &msg) {
|
||||
msg.deliver(*this);
|
||||
});
|
||||
}
|
||||
|
||||
return !timeout_reached;
|
||||
}
|
||||
|
||||
bool BoostThreadWorker::push(std::unique_ptr<Job> job)
|
||||
{
|
||||
if (job)
|
||||
m_input_queue.push(JobEntry{std::move(job)});
|
||||
|
||||
return bool{job};
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
140
src/slic3r/GUI/Jobs/BoostThreadWorker.hpp
Normal file
140
src/slic3r/GUI/Jobs/BoostThreadWorker.hpp
Normal file
@ -0,0 +1,140 @@
|
||||
#ifndef BOOSTTHREADWORKER_HPP
|
||||
#define BOOSTTHREADWORKER_HPP
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
#include "Worker.hpp"
|
||||
|
||||
#include <libslic3r/Thread.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "ThreadSafeQueue.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// An implementation of the Worker interface which uses the boost::thread
|
||||
// API and two thread safe message queues to communicate with the main thread
|
||||
// back and forth. The queue from the main thread to the worker thread holds the
|
||||
// job entries that will be performed on the worker. The other queue holds messages
|
||||
// from the worker to the main thread. These messages include status updates,
|
||||
// finishing operation and arbitrary functiors that need to be performed
|
||||
// on the main thread during the jobs execution, like displaying intermediate
|
||||
// results.
|
||||
class BoostThreadWorker : public Worker, private Job::Ctl
|
||||
{
|
||||
struct JobEntry // Goes into worker and also out of worker as a finalize msg
|
||||
{
|
||||
std::unique_ptr<Job> job;
|
||||
bool canceled = false;
|
||||
std::exception_ptr eptr = nullptr;
|
||||
};
|
||||
|
||||
// A message data for status updates. Only goes from worker to main thread.
|
||||
struct StatusInfo { int status; std::string msg; };
|
||||
|
||||
// An arbitrary callback to be called on the main thread. Only from worker
|
||||
// to main thread.
|
||||
struct MainThreadCallData
|
||||
{
|
||||
std::function<void()> fn;
|
||||
std::promise<void> promise;
|
||||
};
|
||||
|
||||
struct EmptyMessage {};
|
||||
|
||||
class WorkerMessage
|
||||
{
|
||||
public:
|
||||
enum MsgType { Empty, Status, Finalize, MainThreadCall };
|
||||
|
||||
private:
|
||||
boost::variant<EmptyMessage, StatusInfo, JobEntry, MainThreadCallData> m_data;
|
||||
|
||||
public:
|
||||
WorkerMessage() = default;
|
||||
WorkerMessage(int s, std::string txt)
|
||||
: m_data{StatusInfo{s, std::move(txt)}}
|
||||
{}
|
||||
WorkerMessage(JobEntry &&entry) : m_data{std::move(entry)} {}
|
||||
WorkerMessage(MainThreadCallData fn) : m_data{std::move(fn)} {}
|
||||
|
||||
int get_type () const { return m_data.which(); }
|
||||
|
||||
void deliver(BoostThreadWorker &runner);
|
||||
};
|
||||
|
||||
using JobQueue = ThreadSafeQueueSPSC<JobEntry>;
|
||||
using MessageQueue = ThreadSafeQueueSPSC<WorkerMessage>;
|
||||
|
||||
boost::thread m_thread;
|
||||
std::atomic<bool> m_running{false}, m_canceled{false};
|
||||
std::shared_ptr<ProgressIndicator> m_progress;
|
||||
JobQueue m_input_queue; // from main thread to worker
|
||||
MessageQueue m_output_queue; // form worker to main thread
|
||||
std::string m_name;
|
||||
|
||||
void run();
|
||||
|
||||
bool join(int timeout_ms = 0);
|
||||
|
||||
protected:
|
||||
// Implement Job::Ctl interface:
|
||||
|
||||
void update_status(int st, const std::string &msg = "") override;
|
||||
|
||||
bool was_canceled() const override { return m_canceled.load(); }
|
||||
|
||||
std::future<void> call_on_main_thread(std::function<void()> fn) override;
|
||||
|
||||
public:
|
||||
explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
||||
boost::thread::attributes & attr,
|
||||
const char * name = "");
|
||||
|
||||
explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
||||
boost::thread::attributes && attr,
|
||||
const char * name = "")
|
||||
: BoostThreadWorker{std::move(pri), attr, name}
|
||||
{}
|
||||
|
||||
explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
||||
const char * name = "")
|
||||
: BoostThreadWorker{std::move(pri), {}, name}
|
||||
{}
|
||||
|
||||
~BoostThreadWorker();
|
||||
|
||||
BoostThreadWorker(const BoostThreadWorker &) = delete;
|
||||
BoostThreadWorker(BoostThreadWorker &&) = delete;
|
||||
BoostThreadWorker &operator=(const BoostThreadWorker &) = delete;
|
||||
BoostThreadWorker &operator=(BoostThreadWorker &&) = delete;
|
||||
|
||||
bool push(std::unique_ptr<Job> job) override;
|
||||
|
||||
bool is_idle() const override
|
||||
{
|
||||
// The assumption is that jobs can only be queued from a single main
|
||||
// thread from which this method is also called. And the output
|
||||
// messages are also processed only in this calling thread. In that
|
||||
// case, if the input queue is empty, it will remain so during this
|
||||
// function call. If the worker thread is also not running and the
|
||||
// output queue is already processed, we can safely say that the
|
||||
// worker is dormant.
|
||||
return m_input_queue.empty() && !m_running.load() && m_output_queue.empty();
|
||||
}
|
||||
|
||||
void cancel() override { m_canceled.store(true); }
|
||||
void cancel_all() override { m_input_queue.clear(); cancel(); }
|
||||
|
||||
ProgressIndicator * get_pri() { return m_progress.get(); }
|
||||
const ProgressIndicator * get_pri() const { return m_progress.get(); }
|
||||
|
||||
void process_events() override;
|
||||
bool wait_for_current_job(unsigned timeout_ms = 0) override;
|
||||
bool wait_for_idle(unsigned timeout_ms = 0) override;
|
||||
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // BOOSTTHREADWORKER_HPP
|
48
src/slic3r/GUI/Jobs/BusyCursorJob.hpp
Normal file
48
src/slic3r/GUI/Jobs/BusyCursorJob.hpp
Normal file
@ -0,0 +1,48 @@
|
||||
#ifndef BUSYCURSORJOB_HPP
|
||||
#define BUSYCURSORJOB_HPP
|
||||
|
||||
#include "Job.hpp"
|
||||
|
||||
#include <wx/utils.h>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
struct CursorSetterRAII
|
||||
{
|
||||
Job::Ctl &ctl;
|
||||
CursorSetterRAII(Job::Ctl &c) : ctl{c}
|
||||
{
|
||||
ctl.call_on_main_thread([] { wxBeginBusyCursor(); });
|
||||
}
|
||||
~CursorSetterRAII()
|
||||
{
|
||||
ctl.call_on_main_thread([] { wxEndBusyCursor(); });
|
||||
}
|
||||
};
|
||||
|
||||
template<class JobSubclass>
|
||||
class BusyCursored: public Job {
|
||||
JobSubclass m_job;
|
||||
|
||||
public:
|
||||
template<class... Args>
|
||||
BusyCursored(Args &&...args) : m_job{std::forward<Args>(args)...}
|
||||
{}
|
||||
|
||||
void process(Ctl &ctl) override
|
||||
{
|
||||
CursorSetterRAII cursor_setter{ctl};
|
||||
m_job.process(ctl);
|
||||
}
|
||||
|
||||
void finalize(bool canceled, std::exception_ptr &eptr) override
|
||||
{
|
||||
m_job.finalize(canceled, eptr);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // BUSYCURSORJOB_HPP
|
@ -3,6 +3,7 @@
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
@ -102,8 +103,12 @@ void FillBedJob::prepare()
|
||||
p.translation(X) -= p.bed_idx * stride;
|
||||
}
|
||||
|
||||
void FillBedJob::process()
|
||||
void FillBedJob::process(Ctl &ctl)
|
||||
{
|
||||
auto statustxt = _u8L("Filling bed");
|
||||
ctl.call_on_main_thread([this] { prepare(); }).wait();
|
||||
ctl.update_status(0, statustxt);
|
||||
|
||||
if (m_object_idx == -1 || m_selected.empty()) return;
|
||||
|
||||
const GLCanvas3D::ArrangeSettings &settings =
|
||||
@ -114,13 +119,13 @@ void FillBedJob::process()
|
||||
params.min_obj_distance = scaled(settings.distance);
|
||||
|
||||
bool do_stop = false;
|
||||
params.stopcondition = [this, &do_stop]() {
|
||||
return was_canceled() || do_stop;
|
||||
params.stopcondition = [&ctl, &do_stop]() {
|
||||
return ctl.was_canceled() || do_stop;
|
||||
};
|
||||
|
||||
params.progressind = [this](unsigned st) {
|
||||
params.progressind = [this, &ctl, &statustxt](unsigned st) {
|
||||
if (st > 0)
|
||||
update_status(int(m_status_range - st), _(L("Filling bed")));
|
||||
ctl.update_status(int(m_status_range - st) * 100 / status_range(), statustxt);
|
||||
};
|
||||
|
||||
params.on_packed = [&do_stop] (const ArrangePolygon &ap) {
|
||||
@ -130,15 +135,18 @@ void FillBedJob::process()
|
||||
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.")));
|
||||
ctl.update_status(100, ctl.was_canceled() ?
|
||||
_u8L("Bed filling canceled.") :
|
||||
_u8L("Bed filling done."));
|
||||
}
|
||||
|
||||
void FillBedJob::finalize()
|
||||
FillBedJob::FillBedJob() : m_plater{wxGetApp().plater()} {}
|
||||
|
||||
void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||
{
|
||||
// Ignore the arrange result if aborted.
|
||||
if (was_canceled()) return;
|
||||
if (canceled || eptr)
|
||||
return;
|
||||
|
||||
if (m_object_idx == -1) return;
|
||||
|
||||
@ -167,8 +175,6 @@ void FillBedJob::finalize()
|
||||
m_plater->sidebar()
|
||||
.obj_list()->increase_object_instances(m_object_idx, size_t(added_cnt));
|
||||
}
|
||||
|
||||
Job::finalize();
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
@ -7,9 +7,9 @@ namespace Slic3r { namespace GUI {
|
||||
|
||||
class Plater;
|
||||
|
||||
class FillBedJob : public PlaterJob
|
||||
class FillBedJob : public Job
|
||||
{
|
||||
int m_object_idx = -1;
|
||||
int m_object_idx = -1;
|
||||
|
||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||
using ArrangePolygons = arrangement::ArrangePolygons;
|
||||
@ -20,23 +20,20 @@ class FillBedJob : public PlaterJob
|
||||
Points m_bedpts;
|
||||
|
||||
int m_status_range = 0;
|
||||
|
||||
protected:
|
||||
|
||||
void prepare() override;
|
||||
void process() override;
|
||||
Plater *m_plater;
|
||||
|
||||
public:
|
||||
FillBedJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
: PlaterJob{std::move(pri), plater}
|
||||
{}
|
||||
void prepare();
|
||||
void process(Ctl &ctl) override;
|
||||
|
||||
int status_range() const override
|
||||
FillBedJob();
|
||||
|
||||
int status_range() const /*override*/
|
||||
{
|
||||
return m_status_range;
|
||||
}
|
||||
|
||||
void finalize() override;
|
||||
void finalize(bool canceled, std::exception_ptr &e) override;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
@ -1,158 +0,0 @@
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
|
||||
#include "Job.hpp"
|
||||
#include <libslic3r/Thread.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void GUI::Job::run(std::exception_ptr &eptr)
|
||||
{
|
||||
m_running.store(true);
|
||||
try {
|
||||
process();
|
||||
} catch (...) {
|
||||
eptr = std::current_exception();
|
||||
}
|
||||
|
||||
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(wxEVT_THREAD, m_thread_evt_id);
|
||||
evt->SetInt(st);
|
||||
evt->SetString(msg);
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri)
|
||||
: m_progress(std::move(pri))
|
||||
{
|
||||
m_thread_evt_id = wxNewId();
|
||||
|
||||
Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
|
||||
if (m_finalizing) return;
|
||||
|
||||
auto msg = evt.GetString();
|
||||
if (!msg.empty() && !m_worker_error)
|
||||
m_progress->set_status_text(msg.ToUTF8().data());
|
||||
|
||||
if (m_finalized) return;
|
||||
|
||||
m_progress->set_progress(evt.GetInt());
|
||||
if (evt.GetInt() == status_range() || m_worker_error) {
|
||||
// set back the original range and cancel callback
|
||||
m_progress->set_range(m_range);
|
||||
// Make sure progress indicators get the last value of their range
|
||||
// to make sure they close, fade out, whathever
|
||||
m_progress->set_progress(m_range);
|
||||
m_progress->set_cancel_callback();
|
||||
wxEndBusyCursor();
|
||||
|
||||
if (m_worker_error) {
|
||||
m_finalized = true;
|
||||
m_progress->set_status_text("");
|
||||
m_progress->set_progress(m_range);
|
||||
on_exception(m_worker_error);
|
||||
}
|
||||
else {
|
||||
// This is an RAII solution to remember that finalization is
|
||||
// running. The run method calls update_status(status_range(), "")
|
||||
// at the end, which queues up a call to this handler in all cases.
|
||||
// If process also calls update_status with maxed out status arg
|
||||
// it will call this handler twice. It is not a problem unless
|
||||
// yield is called inside the finilize() method, which would
|
||||
// jump out of finalize and call this handler again.
|
||||
struct Finalizing {
|
||||
bool &flag;
|
||||
Finalizing (bool &f): flag(f) { flag = true; }
|
||||
~Finalizing() { flag = false; }
|
||||
} fin(m_finalizing);
|
||||
|
||||
finalize();
|
||||
}
|
||||
|
||||
// dont do finalization again for the same process
|
||||
m_finalized = true;
|
||||
}
|
||||
}, m_thread_evt_id);
|
||||
}
|
||||
|
||||
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;
|
||||
m_finalizing = false;
|
||||
|
||||
// Changing cursor to busy
|
||||
wxBeginBusyCursor();
|
||||
|
||||
try { // Execute the job
|
||||
m_worker_error = nullptr;
|
||||
m_thread = create_thread([this] { this->run(m_worker_error); });
|
||||
} 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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,119 +3,53 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <exception>
|
||||
#include <future>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include <slic3r/GUI/I18N.hpp>
|
||||
|
||||
#include "ProgressIndicator.hpp"
|
||||
|
||||
#include <wx/event.h>
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// 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 Job : public wxEvtHandler
|
||||
{
|
||||
int m_range = 100;
|
||||
int m_thread_evt_id = wxID_ANY;
|
||||
boost::thread m_thread;
|
||||
std::atomic<bool> m_running{false}, m_canceled{false};
|
||||
bool m_finalized = false, m_finalizing = false;
|
||||
std::shared_ptr<ProgressIndicator> m_progress;
|
||||
std::exception_ptr m_worker_error = nullptr;
|
||||
|
||||
void run(std::exception_ptr &);
|
||||
|
||||
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 = "");
|
||||
// A class representing a job that is to be run in the background, not blocking
|
||||
// the main thread. Running it is up to a Worker object (see Worker interface)
|
||||
class Job {
|
||||
public:
|
||||
|
||||
bool was_canceled() const { return m_canceled.load(); }
|
||||
// A controller interface that informs the job about cancellation and
|
||||
// makes it possible for the job to advertise its status.
|
||||
class Ctl {
|
||||
public:
|
||||
virtual ~Ctl() = default;
|
||||
|
||||
// Launched just before start(), a job can use it to prepare internals
|
||||
virtual void prepare() {}
|
||||
// status update, to be used from the work thread (process() method)
|
||||
virtual void update_status(int st, const std::string &msg = "") = 0;
|
||||
|
||||
// The method where the actual work of the job should be defined.
|
||||
virtual void process() = 0;
|
||||
|
||||
// Launched when the job is finished. It refreshes the 3Dscene by def.
|
||||
virtual void finalize() { m_finalized = true; }
|
||||
// Returns true if the job was asked to cancel itself.
|
||||
virtual bool was_canceled() const = 0;
|
||||
|
||||
// Execute a functor on the main thread. Note that the exact time of
|
||||
// execution is hard to determine. This can be used to make modifications
|
||||
// on the UI, like displaying some intermediate results or modify the
|
||||
// cursor.
|
||||
// This function returns a std::future<void> object which enables the
|
||||
// caller to optionally wait for the main thread to finish the function call.
|
||||
virtual std::future<void> call_on_main_thread(std::function<void()> fn) = 0;
|
||||
};
|
||||
|
||||
virtual ~Job() = default;
|
||||
|
||||
// The method where the actual work of the job should be defined. This is
|
||||
// run on the worker thread.
|
||||
virtual void process(Ctl &ctl) = 0;
|
||||
|
||||
// Launched when the job is finished on the UI thread.
|
||||
// If the job was cancelled, the first parameter will have a true value.
|
||||
// Exceptions occuring in process() are redirected from the worker thread
|
||||
// into the main (UI) thread. This method is called from the main thread and
|
||||
// can be overriden to handle these exceptions.
|
||||
virtual void on_exception(const std::exception_ptr &eptr)
|
||||
{
|
||||
if (eptr) std::rethrow_exception(eptr);
|
||||
}
|
||||
|
||||
public:
|
||||
Job(std::shared_ptr<ProgressIndicator> pri);
|
||||
|
||||
bool is_finalized() const { return m_finalized; }
|
||||
|
||||
Job(const Job &) = delete;
|
||||
Job(Job &&) = delete;
|
||||
Job &operator=(const Job &) = delete;
|
||||
Job &operator=(Job &&) = delete;
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
// into the main (UI) thread. This method receives the exception and can
|
||||
// handle it properly. Assign nullptr to this second argument before
|
||||
// function return to prevent further action. Leaving it with a non-null
|
||||
// value will result in rethrowing by the worker.
|
||||
virtual void finalize(bool /*canceled*/, std::exception_ptr &) {}
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
@ -12,11 +12,15 @@ void NotificationProgressIndicator::set_range(int range)
|
||||
|
||||
void NotificationProgressIndicator::set_cancel_callback(CancelFn fn)
|
||||
{
|
||||
m_nm->progress_indicator_set_cancel_callback(std::move(fn));
|
||||
m_cancelfn = std::move(fn);
|
||||
m_nm->progress_indicator_set_cancel_callback(m_cancelfn);
|
||||
}
|
||||
|
||||
void NotificationProgressIndicator::set_progress(int pr)
|
||||
{
|
||||
if (!pr)
|
||||
set_cancel_callback(m_cancelfn);
|
||||
|
||||
m_nm->progress_indicator_set_progress(pr);
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ class NotificationManager;
|
||||
|
||||
class NotificationProgressIndicator: public ProgressIndicator {
|
||||
NotificationManager *m_nm = nullptr;
|
||||
|
||||
CancelFn m_cancelfn;
|
||||
public:
|
||||
|
||||
explicit NotificationProgressIndicator(NotificationManager *nm);
|
||||
|
@ -1,17 +0,0 @@
|
||||
#include "PlaterJob.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
void PlaterJob::on_exception(const std::exception_ptr &eptr)
|
||||
{
|
||||
try {
|
||||
if (eptr)
|
||||
std::rethrow_exception(eptr);
|
||||
} catch (std::exception &e) {
|
||||
show_error(m_plater, _(L("An unexpected error occured")) + ": "+ e.what());
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
@ -1,24 +0,0 @@
|
||||
#ifndef PLATERJOB_HPP
|
||||
#define PLATERJOB_HPP
|
||||
|
||||
#include "Job.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class Plater;
|
||||
|
||||
class PlaterJob : public Job {
|
||||
protected:
|
||||
Plater *m_plater;
|
||||
|
||||
void on_exception(const std::exception_ptr &) override;
|
||||
|
||||
public:
|
||||
|
||||
PlaterJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater):
|
||||
Job{std::move(pri)}, m_plater{plater} {}
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // PLATERJOB_HPP
|
127
src/slic3r/GUI/Jobs/PlaterWorker.hpp
Normal file
127
src/slic3r/GUI/Jobs/PlaterWorker.hpp
Normal file
@ -0,0 +1,127 @@
|
||||
#ifndef PLATERWORKER_HPP
|
||||
#define PLATERWORKER_HPP
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "Worker.hpp"
|
||||
#include "BusyCursorJob.hpp"
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class Plater;
|
||||
|
||||
template<class WorkerSubclass>
|
||||
class PlaterWorker: public Worker {
|
||||
WorkerSubclass m_w;
|
||||
Plater *m_plater;
|
||||
|
||||
class PlaterJob : public Job {
|
||||
std::unique_ptr<Job> m_job;
|
||||
Plater *m_plater;
|
||||
|
||||
public:
|
||||
void process(Ctl &c) override
|
||||
{
|
||||
// Ensure that wxWidgets processing wakes up to handle outgoing
|
||||
// messages in plater's wxIdle handler. Otherwise it might happen
|
||||
// that the message will only be processed when an event like mouse
|
||||
// move comes along which might be too late.
|
||||
struct WakeUpCtl: Ctl {
|
||||
Ctl &ctl;
|
||||
WakeUpCtl(Ctl &c) : ctl{c} {}
|
||||
|
||||
void update_status(int st, const std::string &msg = "") override
|
||||
{
|
||||
wxWakeUpIdle();
|
||||
ctl.update_status(st, msg);
|
||||
}
|
||||
|
||||
bool was_canceled() const override { return ctl.was_canceled(); }
|
||||
|
||||
std::future<void> call_on_main_thread(std::function<void()> fn) override
|
||||
{
|
||||
wxWakeUpIdle();
|
||||
return ctl.call_on_main_thread(std::move(fn));
|
||||
}
|
||||
|
||||
} wctl{c};
|
||||
|
||||
CursorSetterRAII busycursor{wctl};
|
||||
m_job->process(wctl);
|
||||
}
|
||||
|
||||
void finalize(bool canceled, std::exception_ptr &eptr) override
|
||||
{
|
||||
m_job->finalize(canceled, eptr);
|
||||
|
||||
if (eptr) try {
|
||||
std::rethrow_exception(eptr);
|
||||
} catch (std::exception &e) {
|
||||
show_error(m_plater, _L("An unexpected error occured: ") + e.what());
|
||||
eptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
PlaterJob(Plater *p, std::unique_ptr<Job> j)
|
||||
: m_job{std::move(j)}, m_plater{p}
|
||||
{
|
||||
// TODO: decide if disabling slice button during UI job is what we
|
||||
// want.
|
||||
// if (m_plater)
|
||||
// m_plater->sidebar().enable_buttons(false);
|
||||
}
|
||||
|
||||
~PlaterJob() override
|
||||
{
|
||||
// TODO: decide if disabling slice button during UI job is what we want.
|
||||
|
||||
// Reload scene ensures that the slice button gets properly
|
||||
// enabled or disabled after the job finishes, depending on the
|
||||
// state of slicing. This might be an overkill but works for now.
|
||||
// if (m_plater)
|
||||
// m_plater->canvas3D()->reload_scene(false);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
template<class... WorkerArgs>
|
||||
PlaterWorker(Plater *plater, WorkerArgs &&...args)
|
||||
: m_w{std::forward<WorkerArgs>(args)...}, m_plater{plater}
|
||||
{
|
||||
// Ensure that messages from the worker thread to the UI thread are
|
||||
// processed continuously.
|
||||
plater->Bind(wxEVT_IDLE, [this](wxIdleEvent &) {
|
||||
process_events();
|
||||
});
|
||||
}
|
||||
|
||||
// Always package the job argument into a PlaterJob
|
||||
bool push(std::unique_ptr<Job> job) override
|
||||
{
|
||||
return m_w.push(std::make_unique<PlaterJob>(m_plater, std::move(job)));
|
||||
}
|
||||
|
||||
bool is_idle() const override { return m_w.is_idle(); }
|
||||
void cancel() override { m_w.cancel(); }
|
||||
void cancel_all() override { m_w.cancel_all(); }
|
||||
void process_events() override { m_w.process_events(); }
|
||||
bool wait_for_current_job(unsigned timeout_ms = 0) override
|
||||
{
|
||||
return m_w.wait_for_current_job(timeout_ms);
|
||||
}
|
||||
bool wait_for_idle(unsigned timeout_ms = 0) override
|
||||
{
|
||||
return m_w.wait_for_idle(timeout_ms);
|
||||
}
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // PLATERJOB_HPP
|
@ -12,6 +12,8 @@
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
|
||||
#include <slic3r/GUI/I18N.hpp>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
void RotoptimizeJob::prepare()
|
||||
@ -45,20 +47,23 @@ void RotoptimizeJob::prepare()
|
||||
}
|
||||
}
|
||||
|
||||
void RotoptimizeJob::process()
|
||||
void RotoptimizeJob::process(Ctl &ctl)
|
||||
{
|
||||
int prev_status = 0;
|
||||
auto statustxt = _u8L("Searching for optimal orientation");
|
||||
ctl.update_status(0, statustxt);
|
||||
|
||||
auto params =
|
||||
sla::RotOptimizeParams{}
|
||||
.accuracy(m_accuracy)
|
||||
.print_config(&m_default_print_cfg)
|
||||
.statucb([this, &prev_status](int s)
|
||||
.statucb([this, &prev_status, &ctl, &statustxt](int s)
|
||||
{
|
||||
if (s > 0 && s < 100)
|
||||
update_status(prev_status + s / m_selected_object_ids.size(),
|
||||
_(L("Searching for optimal orientation")));
|
||||
ctl.update_status(prev_status + s / m_selected_object_ids.size(),
|
||||
statustxt);
|
||||
|
||||
return !was_canceled();
|
||||
return !ctl.was_canceled();
|
||||
});
|
||||
|
||||
|
||||
@ -71,16 +76,20 @@ void RotoptimizeJob::process()
|
||||
|
||||
prev_status += 100 / m_selected_object_ids.size();
|
||||
|
||||
if (was_canceled()) break;
|
||||
if (ctl.was_canceled()) break;
|
||||
}
|
||||
|
||||
update_status(100, was_canceled() ? _(L("Orientation search canceled.")) :
|
||||
_(L("Orientation found.")));
|
||||
ctl.update_status(100, ctl.was_canceled() ?
|
||||
_u8L("Orientation search canceled.") :
|
||||
_u8L("Orientation found."));
|
||||
}
|
||||
|
||||
void RotoptimizeJob::finalize()
|
||||
RotoptimizeJob::RotoptimizeJob() : m_plater{wxGetApp().plater()} { prepare(); }
|
||||
|
||||
void RotoptimizeJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||
{
|
||||
if (was_canceled()) return;
|
||||
if (canceled || eptr)
|
||||
return;
|
||||
|
||||
for (const ObjRot &objrot : m_selected_object_ids) {
|
||||
ModelObject *o = m_plater->model().objects[size_t(objrot.idx)];
|
||||
@ -111,10 +120,8 @@ void RotoptimizeJob::finalize()
|
||||
// m_plater->find_new_position(o->instances);
|
||||
}
|
||||
|
||||
if (!was_canceled())
|
||||
if (!canceled)
|
||||
m_plater->update();
|
||||
|
||||
Job::finalize();
|
||||
}
|
||||
|
||||
}}
|
||||
|
@ -1,16 +1,17 @@
|
||||
#ifndef ROTOPTIMIZEJOB_HPP
|
||||
#define ROTOPTIMIZEJOB_HPP
|
||||
|
||||
#include "PlaterJob.hpp"
|
||||
#include "Job.hpp"
|
||||
|
||||
#include "libslic3r/SLA/Rotfinder.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
namespace GUI {
|
||||
class Plater;
|
||||
|
||||
class RotoptimizeJob : public PlaterJob
|
||||
class RotoptimizeJob : public Job
|
||||
{
|
||||
using FindFn = std::function<Vec2d(const ModelObject & mo,
|
||||
const sla::RotOptimizeParams ¶ms)>;
|
||||
@ -44,19 +45,16 @@ class RotoptimizeJob : public PlaterJob
|
||||
};
|
||||
|
||||
std::vector<ObjRot> m_selected_object_ids;
|
||||
|
||||
protected:
|
||||
|
||||
void prepare() override;
|
||||
void process() override;
|
||||
Plater *m_plater;
|
||||
|
||||
public:
|
||||
|
||||
RotoptimizeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
: PlaterJob{std::move(pri), plater}
|
||||
{}
|
||||
void prepare();
|
||||
void process(Ctl &ctl) override;
|
||||
|
||||
void finalize() override;
|
||||
RotoptimizeJob();
|
||||
|
||||
void finalize(bool canceled, std::exception_ptr &) override;
|
||||
|
||||
static constexpr size_t get_methods_count() { return std::size(Methods); }
|
||||
|
||||
|
114
src/slic3r/GUI/Jobs/SLAImportDialog.hpp
Normal file
114
src/slic3r/GUI/Jobs/SLAImportDialog.hpp
Normal file
@ -0,0 +1,114 @@
|
||||
#ifndef SLAIMPORTDIALOG_HPP
|
||||
#define SLAIMPORTDIALOG_HPP
|
||||
|
||||
#include "SLAImportJob.hpp"
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/filepicker.h>
|
||||
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
|
||||
//#include "libslic3r/Model.hpp"
|
||||
//#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class SLAImportDialog: public wxDialog, public SLAImportJobView {
|
||||
wxFilePickerCtrl *m_filepicker;
|
||||
wxComboBox *m_import_dropdown, *m_quality_dropdown;
|
||||
|
||||
public:
|
||||
SLAImportDialog(Plater *plater)
|
||||
: wxDialog{plater, wxID_ANY, "Import SLA archive"}
|
||||
{
|
||||
auto szvert = new wxBoxSizer{wxVERTICAL};
|
||||
auto szfilepck = new wxBoxSizer{wxHORIZONTAL};
|
||||
|
||||
m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
|
||||
from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")),
|
||||
"SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP",
|
||||
wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER);
|
||||
szfilepck->Add(m_filepicker, 1);
|
||||
szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
auto szchoices = new wxBoxSizer{wxHORIZONTAL};
|
||||
|
||||
static const std::vector<wxString> inp_choices = {
|
||||
_(L("Import model and profile")),
|
||||
_(L("Import profile only")),
|
||||
_(L("Import model only"))
|
||||
};
|
||||
|
||||
m_import_dropdown = new wxComboBox(
|
||||
this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize,
|
||||
inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
|
||||
|
||||
szchoices->Add(m_import_dropdown);
|
||||
szchoices->Add(new wxStaticText(this, wxID_ANY, _L("Quality") + ": "), 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
static const std::vector<wxString> qual_choices = {
|
||||
_(L("Accurate")),
|
||||
_(L("Balanced")),
|
||||
_(L("Quick"))
|
||||
};
|
||||
|
||||
m_quality_dropdown = new wxComboBox(
|
||||
this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize,
|
||||
qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
|
||||
szchoices->Add(m_quality_dropdown);
|
||||
|
||||
m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
||||
if (get_selection() == Sel::profileOnly)
|
||||
m_quality_dropdown->Disable();
|
||||
else m_quality_dropdown->Enable();
|
||||
});
|
||||
|
||||
szvert->Add(szchoices, 0, wxALL, 5);
|
||||
szvert->AddStretchSpacer(1);
|
||||
auto szbtn = new wxBoxSizer(wxHORIZONTAL);
|
||||
szbtn->Add(new wxButton{this, wxID_CANCEL});
|
||||
szbtn->Add(new wxButton{this, wxID_OK});
|
||||
szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5);
|
||||
|
||||
SetSizerAndFit(szvert);
|
||||
}
|
||||
|
||||
Sel get_selection() const override
|
||||
{
|
||||
int sel = m_import_dropdown->GetSelection();
|
||||
return Sel(std::min(int(Sel::modelOnly), std::max(0, sel)));
|
||||
}
|
||||
|
||||
Vec2i get_marchsq_windowsize() const override
|
||||
{
|
||||
enum { Accurate, Balanced, Fast};
|
||||
|
||||
switch(m_quality_dropdown->GetSelection())
|
||||
{
|
||||
case Fast: return {8, 8};
|
||||
case Balanced: return {4, 4};
|
||||
default:
|
||||
case Accurate:
|
||||
return {2, 2};
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_path() const override
|
||||
{
|
||||
return m_filepicker->GetPath().ToUTF8().data();
|
||||
}
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // SLAIMPORTDIALOG_HPP
|
@ -3,7 +3,6 @@
|
||||
#include "libslic3r/Format/SL1.hpp"
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/NotificationManager.hpp"
|
||||
@ -11,104 +10,10 @@
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/filepicker.h>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
enum class Sel { modelAndProfile, profileOnly, modelOnly};
|
||||
|
||||
class ImportDlg: public wxDialog {
|
||||
wxFilePickerCtrl *m_filepicker;
|
||||
wxComboBox *m_import_dropdown, *m_quality_dropdown;
|
||||
|
||||
public:
|
||||
ImportDlg(Plater *plater)
|
||||
: wxDialog{plater, wxID_ANY, "Import SLA archive"}
|
||||
{
|
||||
auto szvert = new wxBoxSizer{wxVERTICAL};
|
||||
auto szfilepck = new wxBoxSizer{wxHORIZONTAL};
|
||||
|
||||
m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
|
||||
from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")),
|
||||
"SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP",
|
||||
wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER);
|
||||
szfilepck->Add(m_filepicker, 1);
|
||||
szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
auto szchoices = new wxBoxSizer{wxHORIZONTAL};
|
||||
|
||||
static const std::vector<wxString> inp_choices = {
|
||||
_(L("Import model and profile")),
|
||||
_(L("Import profile only")),
|
||||
_(L("Import model only"))
|
||||
};
|
||||
|
||||
m_import_dropdown = new wxComboBox(
|
||||
this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize,
|
||||
inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
|
||||
|
||||
szchoices->Add(m_import_dropdown);
|
||||
szchoices->Add(new wxStaticText(this, wxID_ANY, _L("Quality") + ": "), 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
static const std::vector<wxString> qual_choices = {
|
||||
_(L("Accurate")),
|
||||
_(L("Balanced")),
|
||||
_(L("Quick"))
|
||||
};
|
||||
|
||||
m_quality_dropdown = new wxComboBox(
|
||||
this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize,
|
||||
qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
|
||||
szchoices->Add(m_quality_dropdown);
|
||||
|
||||
m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
||||
if (get_selection() == Sel::profileOnly)
|
||||
m_quality_dropdown->Disable();
|
||||
else m_quality_dropdown->Enable();
|
||||
});
|
||||
|
||||
szvert->Add(szchoices, 0, wxALL, 5);
|
||||
szvert->AddStretchSpacer(1);
|
||||
auto szbtn = new wxBoxSizer(wxHORIZONTAL);
|
||||
szbtn->Add(new wxButton{this, wxID_CANCEL});
|
||||
szbtn->Add(new wxButton{this, wxID_OK});
|
||||
szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5);
|
||||
|
||||
SetSizerAndFit(szvert);
|
||||
}
|
||||
|
||||
Sel get_selection() const
|
||||
{
|
||||
int sel = m_import_dropdown->GetSelection();
|
||||
return Sel(std::min(int(Sel::modelOnly), std::max(0, sel)));
|
||||
}
|
||||
|
||||
Vec2i get_marchsq_windowsize() const
|
||||
{
|
||||
enum { Accurate, Balanced, Fast};
|
||||
|
||||
switch(m_quality_dropdown->GetSelection())
|
||||
{
|
||||
case Fast: return {8, 8};
|
||||
case Balanced: return {4, 4};
|
||||
default:
|
||||
case Accurate:
|
||||
return {2, 2};
|
||||
}
|
||||
}
|
||||
|
||||
wxString get_path() const
|
||||
{
|
||||
return m_filepicker->GetPath();
|
||||
}
|
||||
};
|
||||
|
||||
class SLAImportJob::priv {
|
||||
public:
|
||||
Plater *plater;
|
||||
@ -122,23 +27,28 @@ public:
|
||||
std::string err;
|
||||
ConfigSubstitutions config_substitutions;
|
||||
|
||||
ImportDlg import_dlg;
|
||||
const SLAImportJobView * import_dlg;
|
||||
|
||||
priv(Plater *plt) : plater{plt}, import_dlg{plt} {}
|
||||
priv(Plater *plt, const SLAImportJobView *view) : plater{plt}, import_dlg{view} {}
|
||||
};
|
||||
|
||||
SLAImportJob::SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
: PlaterJob{std::move(pri), plater}, p{std::make_unique<priv>(plater)}
|
||||
{}
|
||||
SLAImportJob::SLAImportJob(const SLAImportJobView *view)
|
||||
: p{std::make_unique<priv>(wxGetApp().plater(), view)}
|
||||
{
|
||||
prepare();
|
||||
}
|
||||
|
||||
SLAImportJob::~SLAImportJob() = default;
|
||||
|
||||
void SLAImportJob::process()
|
||||
void SLAImportJob::process(Ctl &ctl)
|
||||
{
|
||||
auto progr = [this](int s) {
|
||||
auto statustxt = _u8L("Importing SLA archive");
|
||||
ctl.update_status(0, statustxt);
|
||||
|
||||
auto progr = [&ctl, &statustxt](int s) {
|
||||
if (s < 100)
|
||||
update_status(int(s), _(L("Importing SLA archive")));
|
||||
return !was_canceled();
|
||||
ctl.update_status(int(s), statustxt);
|
||||
return !ctl.was_canceled();
|
||||
};
|
||||
|
||||
if (p->path.empty()) return;
|
||||
@ -161,15 +71,15 @@ void SLAImportJob::process()
|
||||
p->err = ex.what();
|
||||
}
|
||||
|
||||
update_status(100, was_canceled() ? _(L("Importing canceled.")) :
|
||||
_(L("Importing done.")));
|
||||
ctl.update_status(100, ctl.was_canceled() ? _u8L("Importing canceled.") :
|
||||
_u8L("Importing done."));
|
||||
}
|
||||
|
||||
void SLAImportJob::reset()
|
||||
{
|
||||
p->sel = Sel::modelAndProfile;
|
||||
p->mesh = {};
|
||||
p->profile = m_plater->sla_print().full_print_config();
|
||||
p->profile = p->plater->sla_print().full_print_config();
|
||||
p->win = {2, 2};
|
||||
p->path.Clear();
|
||||
}
|
||||
@ -178,22 +88,19 @@ void SLAImportJob::prepare()
|
||||
{
|
||||
reset();
|
||||
|
||||
if (p->import_dlg.ShowModal() == wxID_OK) {
|
||||
auto path = p->import_dlg.get_path();
|
||||
auto nm = wxFileName(path);
|
||||
p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath();
|
||||
p->sel = p->import_dlg.get_selection();
|
||||
p->win = p->import_dlg.get_marchsq_windowsize();
|
||||
p->config_substitutions.clear();
|
||||
} else {
|
||||
p->path = "";
|
||||
}
|
||||
auto path = p->import_dlg->get_path();
|
||||
auto nm = wxFileName(path);
|
||||
p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath();
|
||||
p->sel = p->import_dlg->get_selection();
|
||||
p->win = p->import_dlg->get_marchsq_windowsize();
|
||||
p->config_substitutions.clear();
|
||||
}
|
||||
|
||||
void SLAImportJob::finalize()
|
||||
void SLAImportJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||
{
|
||||
// Ignore the arrange result if aborted.
|
||||
if (was_canceled()) return;
|
||||
if (canceled || eptr)
|
||||
return;
|
||||
|
||||
if (!p->err.empty()) {
|
||||
show_error(p->plater, p->err);
|
||||
@ -204,7 +111,7 @@ void SLAImportJob::finalize()
|
||||
std::string name = wxFileName(p->path).GetName().ToUTF8().data();
|
||||
|
||||
if (p->profile.empty()) {
|
||||
m_plater->get_notification_manager()->push_notification(
|
||||
p->plater->get_notification_manager()->push_notification(
|
||||
NotificationType::CustomNotification,
|
||||
NotificationManager::NotificationLevel::WarningNotificationLevel,
|
||||
_L("The imported SLA archive did not contain any presets. "
|
||||
@ -213,7 +120,7 @@ void SLAImportJob::finalize()
|
||||
|
||||
if (p->sel != Sel::modelOnly) {
|
||||
if (p->profile.empty())
|
||||
p->profile = m_plater->sla_print().full_print_config();
|
||||
p->profile = p->plater->sla_print().full_print_config();
|
||||
|
||||
const ModelObjectPtrs& objects = p->plater->model().objects;
|
||||
for (auto object : objects)
|
||||
|
@ -1,22 +1,37 @@
|
||||
#ifndef SLAIMPORTJOB_HPP
|
||||
#define SLAIMPORTJOB_HPP
|
||||
|
||||
#include "PlaterJob.hpp"
|
||||
#include "Job.hpp"
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class SLAImportJob : public PlaterJob {
|
||||
class SLAImportJobView {
|
||||
public:
|
||||
enum Sel { modelAndProfile, profileOnly, modelOnly};
|
||||
|
||||
virtual ~SLAImportJobView() = default;
|
||||
|
||||
virtual Sel get_selection() const = 0;
|
||||
virtual Vec2i get_marchsq_windowsize() const = 0;
|
||||
virtual std::string get_path() const = 0;
|
||||
};
|
||||
|
||||
class Plater;
|
||||
|
||||
class SLAImportJob : public Job {
|
||||
class priv;
|
||||
|
||||
std::unique_ptr<priv> p;
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void process() override;
|
||||
void finalize() override;
|
||||
using Sel = SLAImportJobView::Sel;
|
||||
|
||||
public:
|
||||
SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater);
|
||||
void prepare();
|
||||
void process(Ctl &ctl) override;
|
||||
void finalize(bool canceled, std::exception_ptr &) override;
|
||||
|
||||
SLAImportJob(const SLAImportJobView *);
|
||||
~SLAImportJob();
|
||||
|
||||
void reset();
|
||||
|
123
src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp
Normal file
123
src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp
Normal file
@ -0,0 +1,123 @@
|
||||
#ifndef THREADSAFEQUEUE_HPP
|
||||
#define THREADSAFEQUEUE_HPP
|
||||
|
||||
#include <type_traits>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// Helper structure for overloads of ThreadSafeQueueSPSC::consume_one()
|
||||
// to block if the queue is empty.
|
||||
struct BlockingWait
|
||||
{
|
||||
// Timeout to wait for the arrival of new element into the queue.
|
||||
unsigned timeout_ms = 0;
|
||||
|
||||
// An optional atomic flag to set true if an incoming element gets
|
||||
// consumed. The flag will be atomically set to true when popping the
|
||||
// front of the queue.
|
||||
std::atomic<bool> *pop_flag = nullptr;
|
||||
};
|
||||
|
||||
// A thread safe queue for one producer and one consumer.
|
||||
template<class T,
|
||||
template<class, class...> class Container = std::deque,
|
||||
class... ContainerArgs>
|
||||
class ThreadSafeQueueSPSC
|
||||
{
|
||||
std::queue<T, Container<T, ContainerArgs...>> m_queue;
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_cond_var;
|
||||
public:
|
||||
|
||||
// Consume one element, block if the queue is empty.
|
||||
template<class Fn> bool consume_one(const BlockingWait &blkw, Fn &&fn)
|
||||
{
|
||||
static_assert(!std::is_reference_v<T>, "");
|
||||
static_assert(std::is_default_constructible_v<T>, "");
|
||||
static_assert(std::is_move_assignable_v<T> || std::is_copy_assignable_v<T>, "");
|
||||
|
||||
T el;
|
||||
{
|
||||
std::unique_lock lk{m_mutex};
|
||||
|
||||
auto pred = [this]{ return !m_queue.empty(); };
|
||||
if (blkw.timeout_ms > 0) {
|
||||
auto timeout = std::chrono::milliseconds(blkw.timeout_ms);
|
||||
if (!m_cond_var.wait_for(lk, timeout, pred))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
m_cond_var.wait(lk, pred);
|
||||
|
||||
if constexpr (std::is_move_assignable_v<T>)
|
||||
el = std::move(m_queue.front());
|
||||
else
|
||||
el = m_queue.front();
|
||||
|
||||
m_queue.pop();
|
||||
|
||||
if (blkw.pop_flag)
|
||||
// The optional flag is set before the lock us unlocked.
|
||||
blkw.pop_flag->store(true);
|
||||
}
|
||||
|
||||
fn(el);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Consume one element, return true if consumed, false if queue was empty.
|
||||
template<class Fn> bool consume_one(Fn &&fn)
|
||||
{
|
||||
T el;
|
||||
{
|
||||
std::unique_lock lk{m_mutex};
|
||||
if (!m_queue.empty()) {
|
||||
if constexpr (std::is_move_assignable_v<T>)
|
||||
el = std::move(m_queue.front());
|
||||
else
|
||||
el = m_queue.front();
|
||||
|
||||
m_queue.pop();
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
fn(el);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Push element into the queue.
|
||||
template<class...TArgs> void push(TArgs&&...el)
|
||||
{
|
||||
std::lock_guard lk{m_mutex};
|
||||
m_queue.emplace(std::forward<TArgs>(el)...);
|
||||
m_cond_var.notify_one();
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
std::lock_guard lk{m_mutex};
|
||||
return m_queue.empty();
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
std::lock_guard lk{m_mutex};
|
||||
return m_queue.size();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
std::lock_guard lk{m_mutex};
|
||||
while (!m_queue.empty()) m_queue.pop();
|
||||
}
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // THREADSAFEQUEUE_HPP
|
119
src/slic3r/GUI/Jobs/Worker.hpp
Normal file
119
src/slic3r/GUI/Jobs/Worker.hpp
Normal file
@ -0,0 +1,119 @@
|
||||
#ifndef PRUSALSICER_WORKER_HPP
|
||||
#define PRUSALSICER_WORKER_HPP
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Job.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// An interface of a worker that runs jobs on a dedicated worker thread, one
|
||||
// after the other. It is assumed that every method of this class is called
|
||||
// from the same main thread.
|
||||
class Worker {
|
||||
public:
|
||||
// Queue up a new job after the current one. This call does not block.
|
||||
// Returns false if the job gets discarded.
|
||||
virtual bool push(std::unique_ptr<Job> job) = 0;
|
||||
|
||||
// Returns true if no job is running, the job queue is empty and no job
|
||||
// message is left to be processed. This means that nothing is left to
|
||||
// finalize or take care of in the main thread.
|
||||
virtual bool is_idle() const = 0;
|
||||
|
||||
// Ask the current job gracefully to cancel. This call is not blocking and
|
||||
// the job may or may not cancel eventually, depending on its
|
||||
// implementation. Note that it is not trivial to kill a thread forcefully
|
||||
// and we don't need that.
|
||||
virtual void cancel() = 0;
|
||||
|
||||
// This method will delete the queued jobs and cancel the current one.
|
||||
virtual void cancel_all() = 0;
|
||||
|
||||
// Needs to be called continuously to process events (like status update
|
||||
// or finalizing of jobs) in the main thread. This can be done e.g. in a
|
||||
// wxIdle handler.
|
||||
virtual void process_events() = 0;
|
||||
|
||||
// Wait until the current job finishes. Timeout will only be considered
|
||||
// if not zero. Returns false if timeout is reached but the job has not
|
||||
// finished.
|
||||
virtual bool wait_for_current_job(unsigned timeout_ms = 0) = 0;
|
||||
|
||||
// Wait until the whole job queue finishes. Timeout will only be considered
|
||||
// if not zero. Returns false only if timeout is reached but the worker has
|
||||
// not reached the idle state.
|
||||
virtual bool wait_for_idle(unsigned timeout_ms = 0) = 0;
|
||||
|
||||
// The destructor shall properly close the worker thread.
|
||||
virtual ~Worker() = default;
|
||||
};
|
||||
|
||||
template<class Fn> constexpr bool IsProcessFn = std::is_invocable_v<Fn, Job::Ctl&>;
|
||||
template<class Fn> constexpr bool IsFinishFn = std::is_invocable_v<Fn, bool, std::exception_ptr&>;
|
||||
|
||||
// Helper function to use the worker with arbitrary functors.
|
||||
template<class ProcessFn, class FinishFn,
|
||||
class = std::enable_if_t<IsProcessFn<ProcessFn>>,
|
||||
class = std::enable_if_t<IsFinishFn<FinishFn>> >
|
||||
bool queue_job(Worker &w, ProcessFn fn, FinishFn finishfn)
|
||||
{
|
||||
struct LambdaJob: Job {
|
||||
ProcessFn fn;
|
||||
FinishFn finishfn;
|
||||
|
||||
LambdaJob(ProcessFn pfn, FinishFn ffn)
|
||||
: fn{std::move(pfn)}, finishfn{std::move(ffn)}
|
||||
{}
|
||||
|
||||
void process(Ctl &ctl) override { fn(ctl); }
|
||||
void finalize(bool canceled, std::exception_ptr &eptr) override
|
||||
{
|
||||
finishfn(canceled, eptr);
|
||||
}
|
||||
};
|
||||
|
||||
auto j = std::make_unique<LambdaJob>(std::move(fn), std::move(finishfn));
|
||||
return w.push(std::move(j));
|
||||
}
|
||||
|
||||
template<class ProcessFn, class = std::enable_if_t<IsProcessFn<ProcessFn>>>
|
||||
bool queue_job(Worker &w, ProcessFn fn)
|
||||
{
|
||||
return queue_job(w, std::move(fn), [](bool, std::exception_ptr &) {});
|
||||
}
|
||||
|
||||
inline bool queue_job(Worker &w, std::unique_ptr<Job> j)
|
||||
{
|
||||
return w.push(std::move(j));
|
||||
}
|
||||
|
||||
// Replace the current job queue with a new job. The signature is the same
|
||||
// as for queue_job(). This cancels all jobs and
|
||||
// will not wait. The new job will begin after the queue cancels properly.
|
||||
// Note that this can be called from the UI thread and will not block it if
|
||||
// the jobs take longer to cancel.
|
||||
template<class...Args> bool replace_job(Worker &w, Args&& ...args)
|
||||
{
|
||||
w.cancel_all();
|
||||
return queue_job(w, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Cancel the current job and wait for it to actually be stopped.
|
||||
inline bool stop_current_job(Worker &w, unsigned timeout_ms = 0)
|
||||
{
|
||||
w.cancel();
|
||||
return w.wait_for_current_job(timeout_ms);
|
||||
}
|
||||
|
||||
// Cancel all pending jobs including current one and wait until the worker
|
||||
// becomes idle.
|
||||
inline bool stop_queue(Worker &w, unsigned timeout_ms = 0)
|
||||
{
|
||||
w.cancel_all();
|
||||
return w.wait_for_idle(timeout_ms);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // WORKER_HPP
|
@ -574,7 +574,7 @@ void MainFrame::shutdown()
|
||||
#endif // _WIN32
|
||||
|
||||
if (m_plater != nullptr) {
|
||||
m_plater->stop_jobs();
|
||||
m_plater->get_ui_job_worker().cancel_all();
|
||||
|
||||
// Unbinding of wxWidgets event handling in canvases needs to be done here because on MAC,
|
||||
// when closing the application using Command+Q, a mouse event is triggered after this lambda is completed,
|
||||
@ -1211,7 +1211,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
|
||||
append_menu_item(import_menu, wxID_ANY, _L("Import SL1 / SL1S Archive") + dots, _L("Load an SL1 / Sl1S archive"),
|
||||
[this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr,
|
||||
[this](){return m_plater != nullptr && !m_plater->is_any_job_running(); }, this);
|
||||
[this](){return m_plater != nullptr && m_plater->get_ui_job_worker().is_idle(); }, this);
|
||||
|
||||
import_menu->AppendSeparator();
|
||||
append_menu_item(import_menu, wxID_ANY, _L("Import &Config") + dots + "\tCtrl+L", _L("Load exported configuration file"),
|
||||
|
@ -73,7 +73,10 @@
|
||||
#include "Jobs/FillBedJob.hpp"
|
||||
#include "Jobs/RotoptimizeJob.hpp"
|
||||
#include "Jobs/SLAImportJob.hpp"
|
||||
#include "Jobs/SLAImportDialog.hpp"
|
||||
#include "Jobs/NotificationProgressIndicator.hpp"
|
||||
#include "Jobs/PlaterWorker.hpp"
|
||||
#include "Jobs/BoostThreadWorker.hpp"
|
||||
#include "BackgroundSlicingProcess.hpp"
|
||||
#include "PrintHostDialogs.hpp"
|
||||
#include "ConfigWizard.hpp"
|
||||
@ -1638,54 +1641,12 @@ struct Plater::priv
|
||||
BackgroundSlicingProcess background_process;
|
||||
bool suppressed_backround_processing_update { false };
|
||||
|
||||
// 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. 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_fill_bed_id, m_rotoptimize_id, m_sla_import_id;
|
||||
std::shared_ptr<NotificationProgressIndicator> m_pri;
|
||||
|
||||
void before_start() override { m->background_process.stop(); }
|
||||
|
||||
public:
|
||||
Jobs(priv *_m) :
|
||||
m(_m),
|
||||
m_pri{std::make_shared<NotificationProgressIndicator>(m->notification_manager.get())}
|
||||
{
|
||||
m_arrange_id = add_job(std::make_unique<ArrangeJob>(m_pri, m->q));
|
||||
m_fill_bed_id = add_job(std::make_unique<FillBedJob>(m_pri, m->q));
|
||||
m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m_pri, m->q));
|
||||
m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m_pri, m->q));
|
||||
}
|
||||
|
||||
void arrange()
|
||||
{
|
||||
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()
|
||||
{
|
||||
m->take_snapshot(_L("Optimize Rotation"));
|
||||
start(m_rotoptimize_id);
|
||||
}
|
||||
|
||||
void import_sla_arch()
|
||||
{
|
||||
m->take_snapshot(_L("Import SLA archive"));
|
||||
start(m_sla_import_id);
|
||||
}
|
||||
|
||||
} m_ui_jobs;
|
||||
// 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.
|
||||
PlaterWorker<BoostThreadWorker> m_worker;
|
||||
SLAImportDialog * m_sla_import_dlg;
|
||||
|
||||
bool delayed_scene_refresh;
|
||||
std::string delayed_error_message;
|
||||
@ -1990,7 +1951,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
}))
|
||||
, sidebar(new Sidebar(q))
|
||||
, notification_manager(std::make_unique<NotificationManager>(q))
|
||||
, m_ui_jobs(this)
|
||||
, m_worker{q, std::make_unique<NotificationProgressIndicator>(notification_manager.get()), "ui_worker"}
|
||||
, m_sla_import_dlg{new SLAImportDialog{q}}
|
||||
, delayed_scene_refresh(false)
|
||||
, view_toolbar(GLToolbar::Radio, "View")
|
||||
, collapse_toolbar(GLToolbar::Normal, "Collapse")
|
||||
@ -2926,7 +2888,7 @@ void Plater::priv::remove(size_t obj_idx)
|
||||
if (view3D->is_layers_editing_enabled())
|
||||
view3D->enable_layers_editing(false);
|
||||
|
||||
m_ui_jobs.cancel_all();
|
||||
m_worker.cancel_all();
|
||||
model.delete_object(obj_idx);
|
||||
update();
|
||||
// Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model.
|
||||
@ -2941,7 +2903,7 @@ void Plater::priv::delete_object_from_model(size_t obj_idx)
|
||||
if (! model.objects[obj_idx]->name.empty())
|
||||
snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str());
|
||||
Plater::TakeSnapshot snapshot(q, snapshot_label);
|
||||
m_ui_jobs.cancel_all();
|
||||
m_worker.cancel_all();
|
||||
model.delete_object(obj_idx);
|
||||
update();
|
||||
object_list_changed();
|
||||
@ -2959,7 +2921,7 @@ void Plater::priv::delete_all_objects_from_model()
|
||||
|
||||
view3D->get_canvas3d()->reset_sequential_print_clearance();
|
||||
|
||||
m_ui_jobs.cancel_all();
|
||||
m_worker.cancel_all();
|
||||
|
||||
// Stop and reset the Print content.
|
||||
background_process.reset();
|
||||
@ -2991,7 +2953,7 @@ void Plater::priv::reset()
|
||||
|
||||
view3D->get_canvas3d()->reset_sequential_print_clearance();
|
||||
|
||||
m_ui_jobs.cancel_all();
|
||||
m_worker.cancel_all();
|
||||
|
||||
// Stop and reset the Print content.
|
||||
this->background_process.reset();
|
||||
@ -3292,7 +3254,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
|
||||
// Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState.
|
||||
bool Plater::priv::restart_background_process(unsigned int state)
|
||||
{
|
||||
if (m_ui_jobs.is_any_running()) {
|
||||
if (!m_worker.is_idle()) {
|
||||
// Avoid a race condition
|
||||
return false;
|
||||
}
|
||||
@ -3920,7 +3882,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
|
||||
void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
|
||||
{
|
||||
if (evt.status.percent >= -1) {
|
||||
if (m_ui_jobs.is_any_running()) {
|
||||
if (!m_worker.is_idle()) {
|
||||
// Avoid a race condition
|
||||
return;
|
||||
}
|
||||
@ -4609,7 +4571,7 @@ bool Plater::priv::can_simplify() const
|
||||
|
||||
bool Plater::priv::can_increase_instances() const
|
||||
{
|
||||
if (m_ui_jobs.is_any_running()
|
||||
if (!m_worker.is_idle()
|
||||
|| q->canvas3D()->get_gizmos_manager().is_in_editing_mode())
|
||||
return false;
|
||||
|
||||
@ -4619,7 +4581,7 @@ bool Plater::priv::can_increase_instances() const
|
||||
|
||||
bool Plater::priv::can_decrease_instances() const
|
||||
{
|
||||
if (m_ui_jobs.is_any_running()
|
||||
if (!m_worker.is_idle()
|
||||
|| q->canvas3D()->get_gizmos_manager().is_in_editing_mode())
|
||||
return false;
|
||||
|
||||
@ -4639,7 +4601,7 @@ bool Plater::priv::can_split_to_volumes() const
|
||||
|
||||
bool Plater::priv::can_arrange() const
|
||||
{
|
||||
return !model.objects.empty() && !m_ui_jobs.is_any_running();
|
||||
return !model.objects.empty() && m_worker.is_idle();
|
||||
}
|
||||
|
||||
bool Plater::priv::can_layers_editing() const
|
||||
@ -5093,8 +5055,11 @@ void Plater::add_model(bool imperial_units/* = false*/)
|
||||
|
||||
void Plater::import_sl1_archive()
|
||||
{
|
||||
if (!p->m_ui_jobs.is_any_running())
|
||||
p->m_ui_jobs.import_sla_arch();
|
||||
auto &w = get_ui_job_worker();
|
||||
if (w.is_idle() && p->m_sla_import_dlg->ShowModal() == wxID_OK) {
|
||||
p->take_snapshot(_L("Import SLA archive"));
|
||||
replace_job(w, std::make_unique<SLAImportJob>(p->m_sla_import_dlg));
|
||||
}
|
||||
}
|
||||
|
||||
void Plater::extract_config_from_project()
|
||||
@ -5376,12 +5341,9 @@ bool Plater::load_files(const wxArrayString& filenames)
|
||||
|
||||
void Plater::update() { p->update(); }
|
||||
|
||||
void Plater::stop_jobs() { p->m_ui_jobs.stop_all(); }
|
||||
Worker &Plater::get_ui_job_worker() { return p->m_worker; }
|
||||
|
||||
bool Plater::is_any_job_running() const
|
||||
{
|
||||
return p->m_ui_jobs.is_any_running();
|
||||
}
|
||||
const Worker &Plater::get_ui_job_worker() const { return p->m_worker; }
|
||||
|
||||
void Plater::update_ui_from_settings() { p->update_ui_from_settings(); }
|
||||
|
||||
@ -5422,7 +5384,7 @@ void Plater::remove_selected()
|
||||
return;
|
||||
|
||||
Plater::TakeSnapshot snapshot(this, _L("Delete Selected Objects"));
|
||||
p->m_ui_jobs.cancel_all();
|
||||
get_ui_job_worker().cancel_all();
|
||||
p->view3D->delete_selected();
|
||||
}
|
||||
|
||||
@ -5531,8 +5493,11 @@ void Plater::set_number_of_copies(/*size_t num*/)
|
||||
|
||||
void Plater::fill_bed_with_instances()
|
||||
{
|
||||
if (!p->m_ui_jobs.is_any_running())
|
||||
p->m_ui_jobs.fill_bed();
|
||||
auto &w = get_ui_job_worker();
|
||||
if (w.is_idle()) {
|
||||
p->take_snapshot(_L("Fill bed"));
|
||||
replace_job(w, std::make_unique<FillBedJob>());
|
||||
}
|
||||
}
|
||||
|
||||
bool Plater::is_selection_empty() const
|
||||
@ -5933,8 +5898,14 @@ void Plater::reslice()
|
||||
if (canvas3D()->get_gizmos_manager().is_in_editing_mode(true))
|
||||
return;
|
||||
|
||||
// Stop arrange and (or) optimize rotation tasks.
|
||||
this->stop_jobs();
|
||||
// Stop the running (and queued) UI jobs and only proceed if they actually
|
||||
// get stopped.
|
||||
unsigned timeout_ms = 10000;
|
||||
if (!stop_queue(this->get_ui_job_worker(), timeout_ms)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Could not stop UI job within "
|
||||
<< timeout_ms << " milliseconds timeout!";
|
||||
return;
|
||||
}
|
||||
|
||||
if (printer_technology() == ptSLA) {
|
||||
for (auto& object : model().objects)
|
||||
@ -6398,8 +6369,11 @@ GLCanvas3D* Plater::get_current_canvas3D()
|
||||
|
||||
void Plater::arrange()
|
||||
{
|
||||
if (!p->m_ui_jobs.is_any_running())
|
||||
p->m_ui_jobs.arrange();
|
||||
auto &w = get_ui_job_worker();
|
||||
if (w.is_idle()) {
|
||||
p->take_snapshot(_L("Arrange"));
|
||||
replace_job(w, std::make_unique<ArrangeJob>());
|
||||
}
|
||||
}
|
||||
|
||||
void Plater::set_current_canvas_as_dirty()
|
||||
@ -6570,7 +6544,6 @@ void Plater::suppress_background_process(const bool stop_background_process)
|
||||
void Plater::mirror(Axis axis) { p->mirror(axis); }
|
||||
void Plater::split_object() { p->split_object(); }
|
||||
void Plater::split_volume() { p->split_volume(); }
|
||||
void Plater::optimize_rotation() { if (!p->m_ui_jobs.is_any_running()) p->m_ui_jobs.optimize_rotation(); }
|
||||
void Plater::update_menus() { p->menus.update(); }
|
||||
void Plater::show_action_buttons(const bool ready_to_slice) const { p->show_action_buttons(ready_to_slice); }
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
#include "Jobs/Job.hpp"
|
||||
#include "Jobs/Worker.hpp"
|
||||
#include "Search.hpp"
|
||||
|
||||
class wxButton;
|
||||
@ -177,8 +178,41 @@ public:
|
||||
const wxString& get_last_loaded_gcode() const { return m_last_loaded_gcode; }
|
||||
|
||||
void update();
|
||||
void stop_jobs();
|
||||
bool is_any_job_running() const;
|
||||
|
||||
// Get the worker handling the UI jobs (arrange, fill bed, etc...)
|
||||
// Here is an example of starting up an ad-hoc job:
|
||||
// queue_job(
|
||||
// get_ui_job_worker(),
|
||||
// [](Job::Ctl &ctl) {
|
||||
// // Executed in the worker thread
|
||||
//
|
||||
// CursorSetterRAII cursor_setter{ctl};
|
||||
// std::string msg = "Running";
|
||||
//
|
||||
// ctl.update_status(0, msg);
|
||||
// for (int i = 0; i < 100; i++) {
|
||||
// usleep(100000);
|
||||
// if (ctl.was_canceled()) break;
|
||||
// ctl.update_status(i + 1, msg);
|
||||
// }
|
||||
// ctl.update_status(100, msg);
|
||||
// },
|
||||
// [](bool, std::exception_ptr &e) {
|
||||
// // Executed in UI thread after the work is done
|
||||
//
|
||||
// try {
|
||||
// if (e) std::rethrow_exception(e);
|
||||
// } catch (std::exception &e) {
|
||||
// BOOST_LOG_TRIVIAL(error) << e.what();
|
||||
// }
|
||||
// e = nullptr;
|
||||
// });
|
||||
// This would result in quick run of the progress indicator notification
|
||||
// from 0 to 100. Use replace_job() instead of queue_job() to cancel all
|
||||
// pending jobs.
|
||||
Worker& get_ui_job_worker();
|
||||
const Worker & get_ui_job_worker() const;
|
||||
|
||||
void select_view(const std::string& direction);
|
||||
void select_view_3D(const std::string& name);
|
||||
|
||||
@ -302,7 +336,6 @@ public:
|
||||
void mirror(Axis axis);
|
||||
void split_object();
|
||||
void split_volume();
|
||||
void optimize_rotation();
|
||||
|
||||
bool can_delete() const;
|
||||
bool can_delete_all() const;
|
||||
|
@ -1,6 +1,7 @@
|
||||
get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
add_executable(${_TEST_NAME}_tests
|
||||
${_TEST_NAME}_tests_main.cpp
|
||||
slic3r_jobs_tests.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r_gui libslic3r)
|
||||
|
148
tests/slic3rutils/slic3r_jobs_tests.cpp
Normal file
148
tests/slic3rutils/slic3r_jobs_tests.cpp
Normal file
@ -0,0 +1,148 @@
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "slic3r/GUI/Jobs/BoostThreadWorker.hpp"
|
||||
#include "slic3r/GUI/Jobs/ProgressIndicator.hpp"
|
||||
|
||||
//#include <boost/thread/thread.hpp>
|
||||
|
||||
struct Progress: Slic3r::ProgressIndicator {
|
||||
int range = 100;
|
||||
int pr = 0;
|
||||
std::string statustxt;
|
||||
void set_range(int r) override { range = r; }
|
||||
void set_cancel_callback(CancelFn = CancelFn()) override {}
|
||||
void set_progress(int p) override { pr = p; }
|
||||
void set_status_text(const char *txt) override { statustxt = txt; }
|
||||
int get_range() const override { return range; }
|
||||
};
|
||||
|
||||
TEST_CASE("nullptr job should be ignored", "[Jobs]") {
|
||||
Slic3r::GUI::BoostThreadWorker worker{std::make_unique<Progress>()};
|
||||
worker.push(nullptr);
|
||||
|
||||
REQUIRE(worker.is_idle());
|
||||
}
|
||||
|
||||
TEST_CASE("State should not be idle while running a job", "[Jobs]") {
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
BoostThreadWorker worker{std::make_unique<Progress>(), "worker_thread"};
|
||||
|
||||
queue_job(worker, [&worker](Job::Ctl &ctl) {
|
||||
ctl.call_on_main_thread([&worker] {
|
||||
REQUIRE(!worker.is_idle());
|
||||
}).wait();
|
||||
});
|
||||
|
||||
worker.wait_for_idle();
|
||||
|
||||
REQUIRE(worker.is_idle());
|
||||
}
|
||||
|
||||
TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]") {
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
auto pri = std::make_shared<Progress>();
|
||||
BoostThreadWorker worker{pri};
|
||||
|
||||
queue_job(worker, [](Job::Ctl &ctl){
|
||||
for (int s = 0; s <= 100; ++s) {
|
||||
ctl.update_status(s, "Running");
|
||||
}
|
||||
});
|
||||
|
||||
worker.wait_for_idle();
|
||||
|
||||
REQUIRE(pri->pr == 100);
|
||||
REQUIRE(pri->statustxt == "Running");
|
||||
}
|
||||
|
||||
TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]") {
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
auto pri = std::make_shared<Progress>();
|
||||
BoostThreadWorker worker{pri};
|
||||
|
||||
queue_job(
|
||||
worker,
|
||||
[](Job::Ctl &ctl) {
|
||||
for (int s = 0; s <= 100; ++s) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
ctl.update_status(s, "Running");
|
||||
if (ctl.was_canceled()) break;
|
||||
}
|
||||
},
|
||||
[](bool cancelled, std::exception_ptr &) { // finalize
|
||||
REQUIRE(cancelled == true);
|
||||
});
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
worker.cancel();
|
||||
|
||||
worker.wait_for_current_job();
|
||||
|
||||
REQUIRE(pri->pr != 100);
|
||||
}
|
||||
|
||||
TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") {
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
auto pri = std::make_shared<Progress>();
|
||||
BoostThreadWorker worker{pri};
|
||||
|
||||
std::array<bool, 4> jobres = {false};
|
||||
|
||||
queue_job(worker, [&jobres](Job::Ctl &) {
|
||||
jobres[0] = true;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
});
|
||||
queue_job(worker, [&jobres](Job::Ctl &) {
|
||||
jobres[1] = true;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
});
|
||||
queue_job(worker, [&jobres](Job::Ctl &) {
|
||||
jobres[2] = true;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
});
|
||||
queue_job(worker, [&jobres](Job::Ctl &) {
|
||||
jobres[3] = true;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
});
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(500));
|
||||
worker.cancel_all();
|
||||
|
||||
REQUIRE(jobres[0] == true);
|
||||
REQUIRE(jobres[1] == false);
|
||||
REQUIRE(jobres[2] == false);
|
||||
REQUIRE(jobres[3] == false);
|
||||
}
|
||||
|
||||
TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]") {
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
auto pri = std::make_shared<Progress>();
|
||||
BoostThreadWorker worker{pri};
|
||||
|
||||
queue_job(
|
||||
worker, [](Job::Ctl &) { throw std::runtime_error("test"); },
|
||||
[](bool /*canceled*/, std::exception_ptr &eptr) {
|
||||
REQUIRE(eptr != nullptr);
|
||||
try {
|
||||
std::rethrow_exception(eptr);
|
||||
} catch (std::runtime_error &e) {
|
||||
REQUIRE(std::string(e.what()) == "test");
|
||||
}
|
||||
|
||||
eptr = nullptr;
|
||||
});
|
||||
|
||||
worker.wait_for_idle();
|
||||
REQUIRE(worker.is_idle());
|
||||
}
|
Loading…
Reference in New Issue
Block a user