mirror of
https://github.com/QIDITECH/QIDIStudio.git
synced 2026-02-02 18:08:51 +03:00
update slic3r
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 (...) {
|
||||
;
|
||||
|
||||
182
src/slic3r/GUI/Jobs/BoostThreadWorker.cpp
Normal file
182
src/slic3r/GUI/Jobs/BoostThreadWorker.cpp
Normal 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
|
||||
155
src/slic3r/GUI/Jobs/BoostThreadWorker.hpp
Normal file
155
src/slic3r/GUI/Jobs/BoostThreadWorker.hpp
Normal 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
|
||||
54
src/slic3r/GUI/Jobs/BusyCursorJob.hpp
Normal file
54
src/slic3r/GUI/Jobs/BusyCursorJob.hpp
Normal 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
|
||||
1153
src/slic3r/GUI/Jobs/EmbossJob.cpp
Normal file
1153
src/slic3r/GUI/Jobs/EmbossJob.cpp
Normal file
File diff suppressed because it is too large
Load Diff
351
src/slic3r/GUI/Jobs/EmbossJob.hpp
Normal file
351
src/slic3r/GUI/Jobs/EmbossJob.hpp
Normal 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_
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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); });
|
||||
|
||||
@@ -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);
|
||||
|
||||
68
src/slic3r/GUI/Jobs/JobNew.hpp
Normal file
68
src/slic3r/GUI/Jobs/JobNew.hpp
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ class NotificationManager;
|
||||
|
||||
class NotificationProgressIndicator: public ProgressIndicator {
|
||||
NotificationManager *m_nm = nullptr;
|
||||
CancelFn m_cancelfn;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
155
src/slic3r/GUI/Jobs/PlaterWorker.hpp
Normal file
155
src/slic3r/GUI/Jobs/PlaterWorker.hpp
Normal 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
|
||||
124
src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp
Normal file
124
src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp
Normal 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
|
||||
140
src/slic3r/GUI/Jobs/Worker.hpp
Normal file
140
src/slic3r/GUI/Jobs/Worker.hpp
Normal 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
|
||||
Reference in New Issue
Block a user