Move ui jobs into separate folder

This commit is contained in:
tamasmeszaros 2020-04-23 18:56:09 +02:00
parent 728d90cb33
commit 6eb51a1cca
11 changed files with 19 additions and 18 deletions

View file

@ -0,0 +1,223 @@
#include "ArrangeJob.hpp"
#include "libslic3r/MTUtils.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/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

View file

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

121
src/slic3r/GUI/Jobs/Job.cpp Normal file
View file

@ -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();
});
}
}

110
src/slic3r/GUI/Jobs/Job.hpp Normal file
View file

@ -0,0 +1,110 @@
#ifndef JOB_HPP
#define JOB_HPP
#include <atomic>
#include <slic3r/Utils/Thread.hpp>
#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;
boost::thread m_thread;
std::atomic<bool> m_running{false}, m_canceled{false};
bool m_finalized = false;
std::shared_ptr<ProgressIndicator> m_progress;
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 = "");
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);
bool is_finalized() const { return m_finalized; }
Job(const Job &) = delete;
Job(Job &&) = delete;
Job &operator=(const Job &) = delete;
Job &operator=(Job &&) = delete;
virtual void process() = 0;
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;
};
}} // namespace Slic3r::GUI
#endif // JOB_HPP

View file

@ -0,0 +1,29 @@
#ifndef IPROGRESSINDICATOR_HPP
#define IPROGRESSINDICATOR_HPP
#include <string>
#include <functional>
namespace Slic3r {
/**
* @brief Generic progress indication interface.
*/
class ProgressIndicator {
public:
/// Cancel callback function type
using CancelFn = std::function<void()>;
virtual ~ProgressIndicator() = default;
virtual void set_range(int range) = 0;
virtual void set_cancel_callback(CancelFn = CancelFn()) = 0;
virtual void set_progress(int pr) = 0;
virtual void set_status_text(const char *) = 0; // utf8 char array
virtual int get_range() const = 0;
};
}
#endif // IPROGRESSINDICATOR_HPP

View file

@ -0,0 +1,68 @@
#include "RotoptimizeJob.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/SLA/Rotfinder.hpp"
#include "libslic3r/MinAreaBoundingBox.hpp"
#include "slic3r/GUI/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();
}
}}

View file

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