update slic3r

This commit is contained in:
QIDI TECH
2024-11-28 15:19:12 +08:00
parent a26696f35e
commit 7eb6543991
196 changed files with 18701 additions and 3803 deletions

View File

@@ -1,9 +1,6 @@
#include "ArrangeJob.hpp"
#include "libslic3r/BuildVolume.hpp"
#include "libslic3r/SVG.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/ModelArrange.hpp"
#include "slic3r/GUI/PartPlate.hpp"
@@ -12,11 +9,11 @@
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "libnest2d/common.hpp"
#define SAVE_ARRANGE_POLY 0
#define ARRANGE_LOG(level) BOOST_LOG_TRIVIAL(level) << "arrange: "
namespace Slic3r { namespace GUI {
using ArrangePolygon = arrangement::ArrangePolygon;
@@ -57,7 +54,7 @@ public:
ret.is_wipe_tower = true;
++ret.priority;
BOOST_LOG_TRIVIAL(debug) << " arrange: wipe tower info:" << m_bb << ", m_pos: " << m_pos.transpose();
ARRANGE_LOG(debug) << " wipe tower info:" << m_bb << ", m_pos: " << m_pos.transpose();
return ret;
}
@@ -158,7 +155,7 @@ void ArrangeJob::prepare_selected() {
m_locked.emplace_back(std::move(ap));
if (inst_sel[i])
selected_is_locked = true;
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%, name %3%") % oidx % i % mo->name;
ARRANGE_LOG(debug) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%, name %3%") % oidx % i % mo->name;
}
}
}
@@ -226,14 +223,12 @@ void ArrangeJob::prepare_all() {
ap.itemid = m_locked.size();
m_locked.emplace_back(std::move(ap));
selected_is_locked = true;
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%") % oidx % i;
ARRANGE_LOG(debug) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%") % oidx % i;
}
}
}
// If the selection was empty arrange everything
//if (m_selected.empty()) m_selected.swap(m_unselected);
if (m_selected.empty()) {
if (!selected_is_locked) {
m_plater->get_notification_manager()->push_notification(NotificationType::QDTPlateInfo,
@@ -244,6 +239,13 @@ void ArrangeJob::prepare_all() {
NotificationManager::NotificationLevel::WarningNotificationLevel, into_u8(_L("All the selected objects are on the locked plate,\nWe can not do auto-arrange on these objects.")));
}
}
if (!m_uncompatible_plates.empty()) {
auto msg = _L("The following plates are skipped due to different arranging settings from global:");
for (int i : m_uncompatible_plates) { msg += "\n"+_L("Plate") + " " + std::to_string(i + 1);
}
m_plater->get_notification_manager()->push_notification(NotificationType::QDTPlateInfo,
NotificationManager::NotificationLevel::WarningNotificationLevel, into_u8(msg));
}
prepare_wipe_tower();
@@ -298,7 +300,7 @@ void ArrangeJob::prepare_wipe_tower()
obj_extruders.insert(item.extrude_ids.begin(), item.extrude_ids.end());
if (obj_extruders.size() > 1) {
need_wipe_tower = true;
BOOST_LOG_TRIVIAL(info) << "arrange: need wipe tower because object " << item.name << " has multiple extruders (has paint-on colors)";
ARRANGE_LOG(info) << "need wipe tower because object " << item.name << " has multiple extruders (has paint-on colors)";
break;
}
}
@@ -312,12 +314,12 @@ void ArrangeJob::prepare_wipe_tower()
for (const auto& be : bedTemp2extruderIds) {
if (be.second.size() > 1) {
need_wipe_tower = true;
BOOST_LOG_TRIVIAL(info) << "arrange: need wipe tower because allow_multi_materials_on_same_plate=true and we have multiple extruders of same type";
ARRANGE_LOG(info) << "need wipe tower because allow_multi_materials_on_same_plate=true and we have multiple extruders of same type";
break;
}
}
}
BOOST_LOG_TRIVIAL(info) << "arrange: need_wipe_tower=" << need_wipe_tower;
ARRANGE_LOG(info) << "need_wipe_tower=" << need_wipe_tower;
ArrangePolygon wipe_tower_ap;
@@ -372,7 +374,7 @@ void ArrangeJob::prepare_partplate() {
if (plate->empty())
{
//no instances on this plate
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": no instances in current plate!");
ARRANGE_LOG(info) << __FUNCTION__ << boost::format(": no instances in current plate!");
return;
}
@@ -423,6 +425,75 @@ void ArrangeJob::prepare_partplate() {
plate_list.preprocess_exclude_areas(m_unselected, current_plate_index + 1);
}
void ArrangeJob::prepare_outside_plate() {
clear_input();
std::set<std::pair<int, int>> all_inside_objects;
std::set<std::pair<int, int>> all_outside_objects;
Model &model = m_plater->model();
PartPlateList &plate_list = m_plater->get_partplate_list();
//collect all the objects outside
for (int plate_idx = 0; plate_idx < plate_list.get_plate_count(); plate_idx++) {
PartPlate *plate = plate_list.get_plate(plate_idx);
assert(plate != nullptr);
std::set<std::pair<int, int>>& plate_objects = plate->get_obj_and_inst_set();
std::set<std::pair<int, int>>& plate_outside_objects = plate->get_obj_and_inst_outside_set();
if (plate_objects.empty()) {
// no instances on this plate
ARRANGE_LOG(info) << __FUNCTION__ << format(": no instances in plate %d!", plate_idx);
continue;
}
all_inside_objects.insert(plate_objects.begin(), plate_objects.end());
if (!plate_outside_objects.empty())
all_outside_objects.insert(plate_outside_objects.begin(), plate_outside_objects.end());
if (plate->is_locked()) {
ARRANGE_LOG(info) << __FUNCTION__ << format(": skip locked plate %d!", plate_idx);
continue;
}
// if there are objects inside the plate, lock the plate and don't put new objects in it
if (plate_objects.size() > plate_outside_objects.size()) {
plate->lock(true);
m_uncompatible_plates.push_back(plate_idx);
ARRANGE_LOG(info) << __FUNCTION__ << format(": lock plate %d because there are objects inside!", plate_idx);
}
}
for (int obj_idx = 0; obj_idx < model.objects.size(); obj_idx++) {
ModelObject *object = model.objects[obj_idx];
for (size_t inst_idx = 0; inst_idx < object->instances.size(); ++inst_idx) {
ModelInstance * instance = object->instances[inst_idx];
std::set<std::pair<int, int>>::iterator iter1, iter2;
iter1 = all_inside_objects.find(std::pair(obj_idx, inst_idx));
iter2 = all_outside_objects.find(std::pair(obj_idx, inst_idx));
bool outside_plate = false;
if (iter1 == all_inside_objects.end()) {
//skip
continue;
}
if (iter2 != all_outside_objects.end()) {
outside_plate = true;
}
ArrangePolygon&& ap = prepare_arrange_polygon(instance);
ArrangePolygons &cont = instance->printable ? (outside_plate ? m_selected : m_locked) : m_unprintable;
ap.itemid = cont.size();
if (!outside_plate) {
plate_list.preprocess_arrange_polygon(obj_idx, inst_idx, ap, true);
}
cont.emplace_back(std::move(ap));
}
}
prepare_wipe_tower();
// add the virtual object into unselect list if has
plate_list.preprocess_exclude_areas(m_unselected, current_plate_index + 1);
}
//QDS: add partplate logic
void ArrangeJob::prepare()
{
@@ -449,6 +520,9 @@ void ArrangeJob::prepare()
else if (state == Job::JobPrepareState::PREPARE_STATE_MENU) {
only_on_partplate = true; // only arrange items on current plate
prepare_partplate();
} else if (state == Job::JobPrepareState::PREPARE_STATE_OUTSIDE_BED) {
only_on_partplate = false;
prepare_outside_plate();
}
@@ -456,8 +530,7 @@ void ArrangeJob::prepare()
if (1)
{ // subtract excluded region and get a polygon bed
auto& print = wxGetApp().plater()->get_partplate_list().get_current_fff_print();
auto print_config = print.config();
bed_poly.points = get_bed_shape(*m_plater->config());
bed_poly.points = get_bed_shape(config);
Pointfs excluse_area_points = print_config.bed_exclude_area.values;
Polygons exclude_polys;
Polygon exclude_poly;
@@ -540,12 +613,11 @@ void ArrangeJob::process()
if (params.avoid_extrusion_cali_region && global_config.opt_bool("scan_first_layer"))
partplate_list.preprocess_nonprefered_areas(m_unselected, MAX_NUM_PLATES);
update_arrange_params(params, m_plater->config(), m_selected);
update_selected_items_inflation(m_selected, m_plater->config(), params);
update_unselected_items_inflation(m_unselected, m_plater->config(), params);
update_selected_items_axis_align(m_selected, m_plater->config(), params);
update_arrange_params(params, global_config, m_selected);
update_selected_items_inflation(m_selected, global_config, params);
update_unselected_items_inflation(m_unselected, global_config, params);
Points bedpts = get_shrink_bedpts(m_plater->config(),params);
Points bedpts = get_shrink_bedpts(global_config,params);
partplate_list.preprocess_exclude_areas(params.excluded_regions, 1, scale_(1));
@@ -611,15 +683,16 @@ static std::string concat_strings(const std::set<std::string> &strings,
});
}
void ArrangeJob::finalize() {
void ArrangeJob::finalize()
{
// QDS: partplate
PartPlateList &plate_list = m_plater->get_partplate_list();
// Ignore the arrange result if aborted.
if (!was_canceled()) {
// Unprintable items go to the last virtual bed
int beds = 0;
//QDS: partplate
PartPlateList& plate_list = m_plater->get_partplate_list();
//clear all the relations before apply the arrangement results
if (only_on_partplate) {
plate_list.clear(false, false, true, current_plate_index);

View File

@@ -38,6 +38,9 @@ class ArrangeJob : public PlaterJob
//QDS:prepare the items from current selected partplate
void prepare_partplate();
void prepare_outside_plate();
void prepare_wipe_tower();
ArrangePolygon prepare_arrange_polygon(void* instance);

View File

@@ -116,7 +116,9 @@ void BindJob::process()
try
{
error_code = stoi(result_info);
result_info = wxGetApp().get_hms_query()->query_print_error_msg(error_code).ToStdString();
wxString error_msg;
wxGetApp().get_hms_query()->query_print_error_msg(error_code, error_msg);
result_info = error_msg.ToStdString();
}
catch (...) {
;

View File

@@ -0,0 +1,182 @@
#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<JobNew> job)
{
if (!job)
return false;
m_input_queue.push(JobEntry{std::move(job)});
return true;
}
}} // namespace Slic3r::GUI

View File

@@ -0,0 +1,155 @@
#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"
#include "slic3r/GUI/GUI.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 JobNew::Ctl
{
struct JobEntry // Goes into worker and also out of worker as a finalize msg
{
std::unique_ptr<JobNew> 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<JobNew> 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 clear_percent() override
{
if (m_progress) {
m_progress->clear_percent();
}
}
void show_error_info(const std::string &msg, int code, const std::string &description, const std::string &extra) override
{
if (m_progress) {
m_progress->show_error_info(from_u8(msg), code, from_u8(description), from_u8(extra));
}
}
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

View File

@@ -0,0 +1,54 @@
#ifndef BUSYCURSORJOB_HPP
#define BUSYCURSORJOB_HPP
#include "JobNew.hpp"
#include <wx/utils.h>
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace GUI {
struct CursorSetterRAII
{
JobNew::Ctl &ctl;
CursorSetterRAII(JobNew::Ctl &c) : ctl{c}
{
ctl.call_on_main_thread([] { wxBeginBusyCursor(); });
}
~CursorSetterRAII()
{
try {
ctl.call_on_main_thread([] { wxEndBusyCursor(); });
} catch(...) {
BOOST_LOG_TRIVIAL(error) << "Can't revert cursor from busy to normal";
}
}
};
template<class JobSubclass>
class BusyCursored : public JobNew
{
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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,351 @@
#ifndef slic3r_EmbossJob_hpp_
#define slic3r_EmbossJob_hpp_
#include <atomic>
#include <memory>
#include <string>
#include <functional>
#include <libslic3r/Emboss.hpp>
#include <libslic3r/EmbossShape.hpp> // ExPolygonsWithIds
#include "libslic3r/Point.hpp" // Transform3d
#include "libslic3r/ObjectID.hpp"
#include "slic3r/GUI/Camera.hpp"
//#include "slic3r/GUI/TextLines.hpp"
#include "slic3r/Utils/RaycastManager.hpp"
#include "JobNew.hpp"
namespace Slic3r {
class TriangleMesh;
class ModelVolume;
class ModelObject;
enum class ModelVolumeType : int;
class BuildVolume;
namespace GUI {
//class Plater;
class GLCanvas3D;
class Worker;
class Selection;
namespace Emboss {
class DataBase
{
public:
DataBase(const std::string &volume_name, std::shared_ptr<std::atomic<bool>> cancel) : volume_name(volume_name), cancel(std::move(cancel)) {}
DataBase(const std::string &volume_name, std::shared_ptr<std::atomic<bool>> cancel, EmbossShape &&shape)
: volume_name(volume_name), cancel(std::move(cancel)), shape(std::move(shape))
{}
DataBase(DataBase &&) = default;
virtual ~DataBase() = default;
/// <summary>
/// Create shape
/// e.g. Text extract glyphs from font
/// Not 'const' function because it could modify shape
/// </summary>
virtual EmbossShape &create_shape() { return shape; };
/// <summary>
/// Write data how to reconstruct shape to volume
/// </summary>
/// <param name="volume">Data object for store emboss params</param>
virtual void write(ModelVolume &volume) const;
// Define projection move
// True (raised) .. move outside from surface (MODEL_PART)
// False (engraved).. move into object (NEGATIVE_VOLUME)
bool is_outside = true;
// Define per letter projection on one text line
// [optional] It is not used when empty
Slic3r::Emboss::TextLines text_lines = {};
// [optional] Define distance for surface
// It is used only for flat surface (not cutted)
// Position of Zero(not set value) differ for MODEL_PART and NEGATIVE_VOLUME
std::optional<float> from_surface;
// new volume name
std::string volume_name;
// flag that job is canceled
// for time after process.
std::shared_ptr<std::atomic<bool>> cancel;
// shape to emboss
EmbossShape shape;
};
struct DataCreateVolumeUtil : public DataBase // modfiy bu qds //struct DataCreateVolume : public DataBase
{
// define embossed volume type
ModelVolumeType volume_type;
// parent ModelObject index where to create volume
ObjectID object_id;
// new created volume transformation
Transform3d trmat;
};
using DataBasePtr = std::unique_ptr<DataBase>;
/// <summary>
/// Hold neccessary data to update embossed text object in job
/// </summary>
struct DataUpdate
{
// Hold data about shape
DataBasePtr base;
// unique identifier of volume to change
ObjectID volume_id;
// Used for prevent flooding Undo/Redo stack on slider.
bool make_snapshot;
};
struct CreateVolumeParams
{
GLCanvas3D &canvas;
// Direction of ray into scene
const Camera &camera;
// To put new object on the build volume
const BuildVolume &build_volume;
// used to emplace job for execution
Worker &worker;
// Contain AABB trees from scene
typedef std::function<void()> register_mesh_pick;
register_mesh_pick on_register_mesh_pick{nullptr};
RaycastManager &raycaster;
RaycastManager::AllowVolumes& raycast_condition;
// New created volume type
ModelVolumeType volume_type;
// Define which gizmo open on the success
unsigned char gizmo_type; // GLGizmosManager::EType
// Volume define object to add new volume
const GLVolume *gl_volume;
// Contain AABB trees from scene
// RaycastManager &raycaster;
// Wanted additionl move in Z(emboss) direction of new created volume
std::optional<float> distance = {};
// Wanted additionl rotation around Z of new created volume
std::optional<float> angle = {};
};
struct DataCreateObject
{
// Hold data about shape
DataBasePtr base;
// define position on screen where to create object
Vec2d screen_coor;
// projection property
const Camera &camera;
// shape of bed in case of create volume on bed
std::vector<Vec2d> bed_shape;
// Define which gizmo open on the success
unsigned char gizmo_type;
// additionl rotation around Z axe, given by style settings
std::optional<float> angle = {};
};
/// <summary>
/// Hold neccessary data to create ModelVolume in job
/// Volume is created on the surface of existing volume in object.
/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!!
/// </summary>
struct DataCreateVolume
{
// Hold data about shape
DataBasePtr base;
// define embossed volume type
ModelVolumeType volume_type;
// parent ModelObject index where to create volume
ObjectID object_id;
// new created volume transformation
std::optional<Transform3d> trmat;
// Define which gizmo open on the success
unsigned char gizmo_type;
};
struct SurfaceVolumeData
{
// Transformation of volume inside of object
Transform3d transform;
struct ModelSource
{
// source volumes
std::shared_ptr<const TriangleMesh> mesh;
// Transformation of volume inside of object
Transform3d tr;
};
using ModelSources = std::vector<ModelSource>;
ModelSources sources;
};
/// <summary>
/// Hold neccessary data to update embossed text object in job
/// </summary>
struct UpdateSurfaceVolumeData : public DataUpdate, public SurfaceVolumeData
{};
static bool was_canceled(const JobNew::Ctl &ctl, const DataBase &base);
static bool exception_process(std::exception_ptr &eptr);
static bool finalize(bool canceled, std::exception_ptr &eptr, const DataBase &input);
/// <summary>
/// Update text volume to use surface from object
/// </summary>
class UpdateSurfaceVolumeJob : public JobNew
{
UpdateSurfaceVolumeData m_input;
TriangleMesh m_result;
public:
// move params to private variable
explicit UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
static bool is_use_surfae_error;
};
/// <summary>
/// Update text shape in existing text volume
/// Predict that there is only one runnig(not canceled) instance of it
/// </summary>
class UpdateJob : public JobNew
{
DataUpdate m_input;
TriangleMesh m_result;
public:
// move params to private variable
explicit UpdateJob(DataUpdate &&input);
/// <summary>
/// Create new embossed volume by m_input data and store to m_result
/// </summary>
/// <param name="ctl">Control containing cancel flag</param>
void process(Ctl &ctl) override;
/// <summary>
/// Update volume - change object_id
/// </summary>
/// <param name="canceled">Was process canceled.
/// NOTE: Be carefull it doesn't care about
/// time between finished process and started finalize part.</param>
/// <param name="">unused</param>
void finalize(bool canceled, std::exception_ptr &eptr) override;
/// <summary>
/// Update text volume
/// </summary>
/// <param name="volume">Volume to be updated</param>
/// <param name="mesh">New Triangle mesh for volume</param>
/// <param name="base">Data to write into volume</param>
static void update_volume(ModelVolume *volume, TriangleMesh &&mesh, const DataBase &base);
};
/// <summary>
/// Create new TextObject on the platter
/// Should not be stopped
/// </summary>
class CreateObjectJob : public JobNew
{
DataCreateObject m_input;
TriangleMesh m_result;
Transform3d m_transformation;
public:
explicit CreateObjectJob(DataCreateObject &&input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
struct Texture
{
unsigned id{0};
unsigned width{0};
unsigned height{0};
};
/// <summary>
/// Hold neccessary data to create(cut) volume from surface object in job
/// </summary>
struct CreateSurfaceVolumeData : public SurfaceVolumeData
{
// Hold data about shape
DataBasePtr base;
// define embossed volume type
ModelVolumeType volume_type;
// parent ModelObject index where to create volume
ObjectID object_id;
// Define which gizmo open on the success
unsigned char gizmo_type;
};
class CreateSurfaceVolumeJob : public JobNew
{
CreateSurfaceVolumeData m_input;
TriangleMesh m_result;
public:
explicit CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
class CreateVolumeJob : public JobNew
{
DataCreateVolume m_input;
TriangleMesh m_result;
public:
explicit CreateVolumeJob(DataCreateVolume &&input);
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &eptr) override;
};
static bool check(unsigned char gizmo_type);
static bool check(const DataBase &input, bool check_fontfile, bool use_surface = false);
static bool check(const CreateVolumeParams &input);
static bool check(const DataCreateObject &input);
static bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surface = false);
static bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false);
static bool check(const DataCreateVolume &input, bool is_main_thread = false);
static bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false);
bool start_create_object_job(const CreateVolumeParams &input, DataBasePtr emboss_data, const Vec2d &coor);
bool start_create_volume_without_position(CreateVolumeParams &input, DataBasePtr data);
bool start_create_volume_job( Worker &worker, const ModelObject &object, const std::optional<Transform3d> &volume_tr, DataBasePtr data, ModelVolumeType volume_type, unsigned char gizmo_type);
bool start_create_volume_on_surface_job(CreateVolumeParams &input, DataBasePtr data, const Vec2d &mouse_pos);
bool start_create_volume(CreateVolumeParams &input, DataBasePtr data, const Vec2d &mouse_pos);
static ExPolygons create_shape(DataBase &input);
static TriangleMesh create_mesh_per_glyph(DataBase &input);
static TriangleMesh try_create_mesh(DataBase &input);
static TriangleMesh create_mesh(DataBase &input);
static indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transform3d &tr, const SurfaceVolumeData::ModelSources &sources, DataBase &input);
static TriangleMesh cut_per_glyph_surface(DataBase &input1, const SurfaceVolumeData &input2);
static TriangleMesh cut_surface(DataBase &input1, const SurfaceVolumeData &input2);
static void _update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3d *tr = nullptr);
static void create_volume(
TriangleMesh &&mesh, const ObjectID &object_id, const ModelVolumeType type, const std::optional<Transform3d> &trmat, const DataBase &data, unsigned char gizmo_type);
/// Update text volume
/// </summary>
/// <param name="volume">Volume to be updated</param>
/// <param name="mesh">New Triangle mesh for volume</param>
/// <param name="base">Data to write into volume</param>
bool start_update_volume(DataUpdate &&data, const ModelVolume &volume, const Selection &selection, RaycastManager &raycaster);
/// <summary>
/// Copied triangles from object to be able create mesh for cut surface from
/// </summary>
/// <param name="volume">Define embossed volume</param>
/// <returns>Source data for cut surface from</returns>
SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &volume);
/// <summary>
/// Copied triangles from object to be able create mesh for cut surface from
/// </summary>
/// <param name="volumes">Source object volumes for cut surface from</param>
/// <param name="text_volume_id">Source volume id</param>
/// <returns>Source data for cut surface from</returns>
SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional<size_t> text_volume_id = {});
}
} // namespace Slic3r::GUI
} // namespace Slic3r
#endif // slic3r_EmbossJob_hpp_

View File

@@ -125,7 +125,7 @@ void FillBedJob::prepare()
plate_list.preprocess_exclude_areas(params.excluded_regions, 1, scaled_exclusion_gap);
plate_list.preprocess_exclude_areas(m_unselected);
m_bedpts = get_bed_shape(*m_plater->config());
m_bedpts = get_bed_shape(global_config);
auto &objects = m_plater->model().objects;
/*BoundingBox bedbb = get_extents(m_bedpts);
@@ -200,18 +200,17 @@ void FillBedJob::prepare()
void FillBedJob::process()
{
if (m_object_idx == -1 || m_selected.empty()) return;
const Slic3r::DynamicPrintConfig &global_config = wxGetApp().preset_bundle->full_config();
update_arrange_params(params, m_plater->config(), m_selected);
m_bedpts = get_shrink_bedpts(m_plater->config(), params);
update_arrange_params(params, global_config, m_selected);
m_bedpts = get_shrink_bedpts(global_config, params);
auto &partplate_list = m_plater->get_partplate_list();
auto &print = wxGetApp().plater()->get_partplate_list().get_current_fff_print();
const Slic3r::DynamicPrintConfig& global_config = wxGetApp().preset_bundle->full_config();
if (params.avoid_extrusion_cali_region && global_config.opt_bool("scan_first_layer"))
partplate_list.preprocess_nonprefered_areas(m_unselected, MAX_NUM_PLATES);
update_selected_items_inflation(m_selected, m_plater->config(), params);
update_unselected_items_inflation(m_unselected, m_plater->config(), params);
update_selected_items_inflation(m_selected, global_config, params);
update_unselected_items_inflation(m_unselected, global_config, params);
bool do_stop = false;
params.stopcondition = [this, &do_stop]() {

View File

@@ -62,7 +62,6 @@ GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri)
// 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;
@@ -86,7 +85,7 @@ GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri)
finalize();
}
wxEndBusyCursor();
// dont do finalization again for the same process
m_finalized = true;
}
@@ -96,6 +95,8 @@ GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri)
void GUI::Job::start()
{ // Start the job. No effect if the job is already running
if (!m_running.load()) {
// Changing cursor to busy
wxBeginBusyCursor();
prepare();
// Save the current status indicatior range and push the new one
@@ -110,9 +111,6 @@ void GUI::Job::start()
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); });

View File

@@ -71,7 +71,8 @@ protected:
public:
enum JobPrepareState {
PREPARE_STATE_DEFAULT = 0,
PREPARE_STATE_MENU = 1,
PREPARE_STATE_MENU = 1,
PREPARE_STATE_OUTSIDE_BED = 2,
};
Job(std::shared_ptr<ProgressIndicator> pri);

View File

@@ -0,0 +1,68 @@
#ifndef JOBNEW_HPP
#define JOBNEW_HPP
#include <atomic>
#include <exception>
#include <future>
#include <wx/window.h>
#include "libslic3r/libslic3r.h"
#include "ProgressIndicator.hpp"
namespace Slic3r { namespace GUI {
// 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 JobNew {
public:
enum JobPrepareState {
PREPARE_STATE_DEFAULT = 0,
PREPARE_STATE_MENU = 1,
};
// 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;
// status update, to be used from the work thread (process() method)
virtual void update_status(int st, const std::string &msg = "") = 0;
// Returns true if the job was asked to cancel itself.
virtual bool was_canceled() const = 0;
// Orca:
virtual void clear_percent() = 0;
virtual void show_error_info(const std::string &msg, int code, const std::string &description, const std::string &extra) = 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 ~JobNew() = 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 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
#endif // JOB_HPP

View File

@@ -22,11 +22,14 @@ 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);
}

View File

@@ -9,6 +9,7 @@ class NotificationManager;
class NotificationProgressIndicator: public ProgressIndicator {
NotificationManager *m_nm = nullptr;
CancelFn m_cancelfn;
public:

View File

@@ -0,0 +1,155 @@
#ifndef PLATERWORKER_HPP
#define PLATERWORKER_HPP
#include <map>
#include <chrono>
#include "Worker.hpp"
#include "BusyCursorJob.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/I18N.hpp"
namespace Slic3r { namespace GUI {
template<class WorkerSubclass>
class PlaterWorker: public Worker {
WorkerSubclass m_w;
wxWindow *m_plater;
class PlaterJob : public JobNew {
std::unique_ptr<JobNew> m_job;
wxWindow *m_plater;
long long m_process_duration; // [ms]
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
{
ctl.update_status(st, msg);
wxWakeUpIdle();
}
bool was_canceled() const override { return ctl.was_canceled(); }
std::future<void> call_on_main_thread(std::function<void()> fn) override
{
auto ftr = ctl.call_on_main_thread(std::move(fn));
wxWakeUpIdle();
return ftr;
}
void clear_percent() override {
ctl.clear_percent();
wxWakeUpIdle();
}
void show_error_info(const std::string &msg, int code, const std::string &description, const std::string &extra) override
{
ctl.show_error_info(msg, code, description, extra);
wxWakeUpIdle();
}
} wctl{c};
CursorSetterRAII busycursor{wctl};
using namespace std::chrono;
steady_clock::time_point process_start = steady_clock::now();
m_job->process(wctl);
steady_clock::time_point process_end = steady_clock::now();
m_process_duration = duration_cast<milliseconds>(process_end - process_start).count();
}
void finalize(bool canceled, std::exception_ptr &eptr) override
{
using namespace std::chrono;
steady_clock::time_point finalize_start = steady_clock::now();
m_job->finalize(canceled, eptr);
steady_clock::time_point finalize_end = steady_clock::now();
long long finalize_duration = duration_cast<milliseconds>(finalize_end - finalize_start).count();
BOOST_LOG_TRIVIAL(info)
<< std::fixed // do not use scientific notations
<< "Job '" << typeid(*m_job).name() << "' "
<< "spend " << m_process_duration + finalize_duration << "ms "
<< "(process " << m_process_duration << "ms + finalize " << finalize_duration << "ms)";
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(wxWindow *p, std::unique_ptr<JobNew> 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);
}
};
EventGuard on_idle_evt;
EventGuard on_paint_evt;
public:
template<class... WorkerArgs>
PlaterWorker(wxWindow *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.
, on_idle_evt(plater, wxEVT_IDLE, [this](wxIdleEvent&) { process_events(); })
, on_paint_evt(plater, wxEVT_PAINT, [this](wxPaintEvent&) { process_events(); })
{
}
// Always package the job argument into a PlaterJob
bool push(std::unique_ptr<JobNew> 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

View File

@@ -0,0 +1,124 @@
#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

View File

@@ -0,0 +1,140 @@
#ifndef PRUSALSICER_WORKER_HPP
#define PRUSALSICER_WORKER_HPP
#include <memory>
#include "JobNew.hpp"
namespace Slic3r { namespace GUI {
// #define EXECUTE_UPDATE_ON_MAIN_THREAD // debug execution on main thread
// 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.
#ifdef EXECUTE_UPDATE_ON_MAIN_THREAD
namespace {
// Run Job on main thread (blocking) - ONLY DEBUG
static inline bool execute_job(std::shared_ptr<JobNew> j)
{
struct MyCtl : public JobNew::Ctl
{
void update_status(int st, const std::string &msg = "") override{};
bool was_canceled() const override { return false; }
std::future<void> call_on_main_thread(std::function<void()> fn) override { return std::future<void>{}; }
} ctl;
j->process(ctl);
wxGetApp().plater()->CallAfter([j]() {
std::exception_ptr e_ptr = nullptr;
j->finalize(false, e_ptr);
});
return true;
}
} // namespace
#endif
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<JobNew> 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, JobNew::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: JobNew {
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<JobNew> 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