Merge branch 'tm_ui_jobs'
This commit is contained in:
commit
2d1f13620c
8 changed files with 362 additions and 153 deletions
|
@ -5,6 +5,7 @@
|
||||||
#include <mutex> // for std::lock_guard
|
#include <mutex> // for std::lock_guard
|
||||||
#include <functional> // for std::function
|
#include <functional> // for std::function
|
||||||
#include <utility> // for std::forward
|
#include <utility> // for std::forward
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
|
@ -182,6 +183,14 @@ public:
|
||||||
inline bool empty() const { return size() == 0; }
|
inline bool empty() const { return size() == 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<class C> bool all_of(const C &container) {
|
||||||
|
return std::all_of(container.begin(),
|
||||||
|
container.end(),
|
||||||
|
[](const typename C::value_type &v) {
|
||||||
|
return static_cast<bool>(v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // MTUTILS_HPP
|
#endif // MTUTILS_HPP
|
||||||
|
|
|
@ -44,7 +44,7 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
|
||||||
// call the status callback in each iteration but the actual value may be
|
// call the status callback in each iteration but the actual value may be
|
||||||
// the same for subsequent iterations (status goes from 0 to 100 but
|
// the same for subsequent iterations (status goes from 0 to 100 but
|
||||||
// iterations can be many more)
|
// iterations can be many more)
|
||||||
auto objfunc = [&emesh, &status, &statuscb, max_tries]
|
auto objfunc = [&emesh, &status, &statuscb, &stopcond, max_tries]
|
||||||
(double rx, double ry, double rz)
|
(double rx, double ry, double rz)
|
||||||
{
|
{
|
||||||
EigenMesh3D& m = emesh;
|
EigenMesh3D& m = emesh;
|
||||||
|
@ -91,7 +91,7 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
|
||||||
}
|
}
|
||||||
|
|
||||||
// report status
|
// report status
|
||||||
statuscb( unsigned(++status * 100.0/max_tries) );
|
if(!stopcond()) statuscb( unsigned(++status * 100.0/max_tries) );
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
};
|
};
|
||||||
|
|
|
@ -54,7 +54,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
// initialize status bar
|
// initialize status bar
|
||||||
m_statusbar = new ProgressStatusBar(this);
|
m_statusbar.reset(new ProgressStatusBar(this));
|
||||||
m_statusbar->embed(this);
|
m_statusbar->embed(this);
|
||||||
m_statusbar->set_status_text(_(L("Version")) + " " +
|
m_statusbar->set_status_text(_(L("Version")) + " " +
|
||||||
SLIC3R_VERSION +
|
SLIC3R_VERSION +
|
||||||
|
@ -103,6 +103,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
||||||
event.Veto();
|
event.Veto();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(m_plater) m_plater->stop_jobs();
|
||||||
|
|
||||||
// Weird things happen as the Paint messages are floating around the windows being destructed.
|
// Weird things happen as the Paint messages are floating around the windows being destructed.
|
||||||
// Avoid the Paint messages by hiding the main window.
|
// Avoid the Paint messages by hiding the main window.
|
||||||
|
@ -138,6 +140,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
||||||
update_ui_from_settings(); // FIXME (?)
|
update_ui_from_settings(); // FIXME (?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MainFrame::~MainFrame() = default;
|
||||||
|
|
||||||
void MainFrame::update_title()
|
void MainFrame::update_title()
|
||||||
{
|
{
|
||||||
wxString title = wxEmptyString;
|
wxString title = wxEmptyString;
|
||||||
|
|
|
@ -89,7 +89,7 @@ protected:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MainFrame();
|
MainFrame();
|
||||||
~MainFrame() {}
|
~MainFrame();
|
||||||
|
|
||||||
Plater* plater() { return m_plater; }
|
Plater* plater() { return m_plater; }
|
||||||
|
|
||||||
|
@ -126,7 +126,7 @@ public:
|
||||||
Plater* m_plater { nullptr };
|
Plater* m_plater { nullptr };
|
||||||
wxNotebook* m_tabpanel { nullptr };
|
wxNotebook* m_tabpanel { nullptr };
|
||||||
wxProgressDialog* m_progress_dialog { nullptr };
|
wxProgressDialog* m_progress_dialog { nullptr };
|
||||||
ProgressStatusBar* m_statusbar { nullptr };
|
std::unique_ptr<ProgressStatusBar> m_statusbar;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // GUI
|
} // GUI
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
#include <future>
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
|
@ -1253,8 +1254,220 @@ struct Plater::priv
|
||||||
Preview *preview;
|
Preview *preview;
|
||||||
|
|
||||||
BackgroundSlicingProcess background_process;
|
BackgroundSlicingProcess background_process;
|
||||||
bool arranging;
|
|
||||||
bool rotoptimizing;
|
// A class to handle UI jobs like arranging and optimizing rotation.
|
||||||
|
// These are not instant jobs, the user has to be informed about their
|
||||||
|
// state in the status progress indicator. On the other hand they are
|
||||||
|
// separated from the background slicing process. Ideally, these jobs should
|
||||||
|
// run when the background process is not running.
|
||||||
|
//
|
||||||
|
// TODO: A mechanism would be useful for blocking the plater interactions:
|
||||||
|
// objects would be frozen for the user. In case of arrange, an animation
|
||||||
|
// could be shown, or with the optimize orientations, partial results
|
||||||
|
// could be displayed.
|
||||||
|
class Job: public wxEvtHandler {
|
||||||
|
int m_range = 100;
|
||||||
|
std::future<void> m_ftr;
|
||||||
|
priv *m_plater = nullptr;
|
||||||
|
std::atomic<bool> m_running {false}, m_canceled {false};
|
||||||
|
bool m_finalized = false;
|
||||||
|
|
||||||
|
void run() {
|
||||||
|
m_running.store(true); process(); m_running.store(false);
|
||||||
|
|
||||||
|
// ensure to call the last status to finalize the job
|
||||||
|
update_status(status_range(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
// status range for a particular job
|
||||||
|
virtual int status_range() const { return 100; }
|
||||||
|
|
||||||
|
// status update, to be used from the work thread (process() method)
|
||||||
|
void update_status(int st, const wxString& msg = "") {
|
||||||
|
auto evt = new wxThreadEvent(); evt->SetInt(st); evt->SetString(msg);
|
||||||
|
wxQueueEvent(this, evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
priv& plater() { return *m_plater; }
|
||||||
|
bool was_canceled() const { return m_canceled.load(); }
|
||||||
|
|
||||||
|
// Launched just before start(), a job can use it to prepare internals
|
||||||
|
virtual void prepare() {}
|
||||||
|
|
||||||
|
// Launched when the job is finished. It refreshes the 3dscene by def.
|
||||||
|
virtual void finalize() {
|
||||||
|
// Do a full refresh of scene tree, including regenerating
|
||||||
|
// all the GLVolumes. FIXME The update function shall just
|
||||||
|
// reload the modified matrices.
|
||||||
|
if(! was_canceled())
|
||||||
|
plater().update(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Job(priv *_plater): m_plater(_plater)
|
||||||
|
{
|
||||||
|
Bind(wxEVT_THREAD, [this](const wxThreadEvent& evt){
|
||||||
|
auto msg = evt.GetString();
|
||||||
|
if(! msg.empty()) plater().statusbar()->set_status_text(msg);
|
||||||
|
|
||||||
|
if(m_finalized) return;
|
||||||
|
|
||||||
|
plater().statusbar()->set_progress(evt.GetInt());
|
||||||
|
if(evt.GetInt() == status_range()) {
|
||||||
|
|
||||||
|
// set back the original range and cancel callback
|
||||||
|
plater().statusbar()->set_range(m_range);
|
||||||
|
plater().statusbar()->set_cancel_callback();
|
||||||
|
wxEndBusyCursor();
|
||||||
|
|
||||||
|
finalize();
|
||||||
|
|
||||||
|
// dont do finalization again for the same process
|
||||||
|
m_finalized = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Job(const Job&) = delete;
|
||||||
|
Job(Job&&) = default;
|
||||||
|
Job& operator=(const Job&) = delete;
|
||||||
|
Job& operator=(Job&&) = default;
|
||||||
|
|
||||||
|
virtual void process() = 0;
|
||||||
|
|
||||||
|
void start() { // Start the job. No effect if the job is already running
|
||||||
|
if(! m_running.load()) {
|
||||||
|
|
||||||
|
prepare();
|
||||||
|
|
||||||
|
// Save the current status indicatior range and push the new one
|
||||||
|
m_range = plater().statusbar()->get_range();
|
||||||
|
plater().statusbar()->set_range(status_range());
|
||||||
|
|
||||||
|
// init cancellation flag and set the cancel callback
|
||||||
|
m_canceled.store(false);
|
||||||
|
plater().statusbar()->set_cancel_callback( [this](){
|
||||||
|
m_canceled.store(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_finalized = false;
|
||||||
|
|
||||||
|
// Changing cursor to busy
|
||||||
|
wxBeginBusyCursor();
|
||||||
|
|
||||||
|
try { // Execute the job
|
||||||
|
m_ftr = std::async(std::launch::async, &Job::run, this);
|
||||||
|
} catch(std::exception& ) {
|
||||||
|
update_status(status_range(),
|
||||||
|
_(L("ERROR: not enough resources to execute a new job.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The state changes will be undone when the process hits the
|
||||||
|
// last status value, in the status update handler (see ctor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// To wait for the running job and join the threads. False is returned
|
||||||
|
// if the timeout has been reached and the job is still running. Call
|
||||||
|
// cancel() before this fn if you want to explicitly end the job.
|
||||||
|
bool join(int timeout_ms = 0) const {
|
||||||
|
if(!m_ftr.valid()) return true;
|
||||||
|
|
||||||
|
if(timeout_ms <= 0)
|
||||||
|
m_ftr.wait();
|
||||||
|
else if(m_ftr.wait_for(std::chrono::milliseconds(timeout_ms)) ==
|
||||||
|
std::future_status::timeout)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_running() const { return m_running.load(); }
|
||||||
|
void cancel() { m_canceled.store(true); }
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Jobs : size_t {
|
||||||
|
Arrange,
|
||||||
|
Rotoptimize
|
||||||
|
};
|
||||||
|
|
||||||
|
// Jobs defined inside the group class will be managed so that only one can
|
||||||
|
// run at a time. Also, the background process will be stopped if a job is
|
||||||
|
// started.
|
||||||
|
class ExclusiveJobGroup {
|
||||||
|
|
||||||
|
static const int ABORT_WAIT_MAX_MS = 10000;
|
||||||
|
|
||||||
|
priv * m_plater;
|
||||||
|
|
||||||
|
class ArrangeJob : public Job
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void prepare() override
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
for (auto obj : plater().model.objects)
|
||||||
|
count += int(obj->instances.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Job::Job;
|
||||||
|
int status_range() const override { return count; }
|
||||||
|
void set_count(int c) { count = c; }
|
||||||
|
void process() override;
|
||||||
|
} arrange_job{m_plater};
|
||||||
|
|
||||||
|
class RotoptimizeJob : public Job
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Job::Job;
|
||||||
|
void process() override;
|
||||||
|
} rotoptimize_job{m_plater};
|
||||||
|
|
||||||
|
std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job,
|
||||||
|
rotoptimize_job};
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
ExclusiveJobGroup(priv *_plater): m_plater(_plater) {}
|
||||||
|
|
||||||
|
void start(Jobs jid) {
|
||||||
|
m_plater->background_process.stop();
|
||||||
|
stop_all();
|
||||||
|
m_jobs[size_t(jid)].get().start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancel_all() { for (Job& j : m_jobs) j.cancel(); }
|
||||||
|
|
||||||
|
void join_all(int wait_ms = 0)
|
||||||
|
{
|
||||||
|
std::vector<bool> aborted(m_jobs.size(), false);
|
||||||
|
|
||||||
|
for (size_t jid = 0; jid < m_jobs.size(); ++jid)
|
||||||
|
aborted[jid] = m_jobs[jid].get().join(wait_ms);
|
||||||
|
|
||||||
|
if (!all_of(aborted))
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Could not abort a job!";
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); }
|
||||||
|
|
||||||
|
const Job& get(Jobs jobid) const { return m_jobs[size_t(jobid)]; }
|
||||||
|
|
||||||
|
bool is_any_running() const
|
||||||
|
{
|
||||||
|
return std::any_of(m_jobs.begin(),
|
||||||
|
m_jobs.end(),
|
||||||
|
[](const Job &j) { return j.is_running(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
} m_ui_jobs{this};
|
||||||
|
|
||||||
bool delayed_scene_refresh;
|
bool delayed_scene_refresh;
|
||||||
std::string delayed_error_message;
|
std::string delayed_error_message;
|
||||||
|
|
||||||
|
@ -1429,8 +1642,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||||
{
|
{
|
||||||
this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||||
|
|
||||||
arranging = false;
|
|
||||||
rotoptimizing = false;
|
|
||||||
background_process.set_fff_print(&fff_print);
|
background_process.set_fff_print(&fff_print);
|
||||||
background_process.set_sla_print(&sla_print);
|
background_process.set_sla_print(&sla_print);
|
||||||
background_process.set_gcode_preview_data(&gcode_preview_data);
|
background_process.set_gcode_preview_data(&gcode_preview_data);
|
||||||
|
@ -1605,7 +1816,7 @@ void Plater::priv::update_ui_from_settings()
|
||||||
|
|
||||||
ProgressStatusBar* Plater::priv::statusbar()
|
ProgressStatusBar* Plater::priv::statusbar()
|
||||||
{
|
{
|
||||||
return main_frame->m_statusbar;
|
return main_frame->m_statusbar.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Plater::priv::get_config(const std::string &key) const
|
std::string Plater::priv::get_config(const std::string &key) const
|
||||||
|
@ -2142,59 +2353,45 @@ void Plater::priv::mirror(Axis axis)
|
||||||
|
|
||||||
void Plater::priv::arrange()
|
void Plater::priv::arrange()
|
||||||
{
|
{
|
||||||
if (arranging) { return; }
|
m_ui_jobs.start(Jobs::Arrange);
|
||||||
arranging = true;
|
}
|
||||||
Slic3r::ScopeGuard arranging_guard([this]() { arranging = false; });
|
|
||||||
|
|
||||||
wxBusyCursor wait;
|
// This method will find an optimal orientation for the currently selected item
|
||||||
|
// Very similar in nature to the arrange method above...
|
||||||
|
void Plater::priv::sla_optimize_rotation() {
|
||||||
|
m_ui_jobs.start(Jobs::Rotoptimize);
|
||||||
|
}
|
||||||
|
|
||||||
this->background_process.stop();
|
void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() {
|
||||||
|
// TODO: we should decide whether to allow arrange when the search is
|
||||||
|
// running we should probably disable explicit slicing and background
|
||||||
|
// processing
|
||||||
|
|
||||||
unsigned count = 0;
|
static const auto arrangestr = _(L("Arranging"));
|
||||||
for(auto obj : model.objects) count += obj->instances.size();
|
|
||||||
|
|
||||||
auto prev_range = statusbar()->get_range();
|
auto &config = plater().config;
|
||||||
statusbar()->set_range(count);
|
auto &view3D = plater().view3D;
|
||||||
|
auto &model = plater().model;
|
||||||
auto statusfn = [this, count] (unsigned st, const std::string& msg) {
|
|
||||||
/* // In case we would run the arrange asynchronously
|
|
||||||
wxCommandEvent event(EVT_PROGRESS_BAR);
|
|
||||||
event.SetInt(st);
|
|
||||||
event.SetString(msg);
|
|
||||||
wxQueueEvent(this->q, event.Clone()); */
|
|
||||||
statusbar()->set_progress(count - st);
|
|
||||||
statusbar()->set_status_text(_(msg));
|
|
||||||
|
|
||||||
// ok, this is dangerous, but we are protected by the flag
|
|
||||||
// 'arranging' and the arrange button is also disabled.
|
|
||||||
// This call is needed for the cancel button to work.
|
|
||||||
wxYieldIfNeeded();
|
|
||||||
};
|
|
||||||
|
|
||||||
statusbar()->set_cancel_callback([this, statusfn](){
|
|
||||||
arranging = false;
|
|
||||||
statusfn(0, L("Arranging canceled"));
|
|
||||||
});
|
|
||||||
|
|
||||||
static const std::string arrangestr = L("Arranging");
|
|
||||||
|
|
||||||
// FIXME: I don't know how to obtain the minimum distance, it depends
|
// FIXME: I don't know how to obtain the minimum distance, it depends
|
||||||
// on printer technology. I guess the following should work but it crashes.
|
// on printer technology. I guess the following should work but it crashes.
|
||||||
double dist = 6; //PrintConfig::min_object_distance(config);
|
double dist = 6; // PrintConfig::min_object_distance(config);
|
||||||
if(printer_technology == ptFFF) {
|
if (plater().printer_technology == ptFFF) {
|
||||||
dist = PrintConfig::min_object_distance(config);
|
dist = PrintConfig::min_object_distance(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto min_obj_distance = coord_t(dist/SCALING_FACTOR);
|
auto min_obj_distance = coord_t(dist / SCALING_FACTOR);
|
||||||
|
|
||||||
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
|
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>(
|
||||||
|
"bed_shape");
|
||||||
|
|
||||||
assert(bed_shape_opt);
|
assert(bed_shape_opt);
|
||||||
auto& bedpoints = bed_shape_opt->values;
|
auto & bedpoints = bed_shape_opt->values;
|
||||||
Polyline bed; bed.points.reserve(bedpoints.size());
|
Polyline bed;
|
||||||
for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
bed.points.reserve(bedpoints.size());
|
||||||
|
for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
||||||
|
|
||||||
statusfn(0, arrangestr);
|
update_status(0, arrangestr);
|
||||||
|
|
||||||
arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info();
|
arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info();
|
||||||
|
|
||||||
|
@ -2210,129 +2407,116 @@ void Plater::priv::arrange()
|
||||||
bed,
|
bed,
|
||||||
hint,
|
hint,
|
||||||
false, // create many piles not just one pile
|
false, // create many piles not just one pile
|
||||||
[statusfn](unsigned st) { statusfn(st, arrangestr); },
|
[this](unsigned st) {
|
||||||
[this] () { return !arranging; });
|
if (st > 0)
|
||||||
} catch(std::exception& /*e*/) {
|
update_status(count - int(st), arrangestr);
|
||||||
GUI::show_error(this->q, L("Could not arrange model objects! "
|
},
|
||||||
"Some geometries may be invalid."));
|
[this]() { return was_canceled(); });
|
||||||
|
} catch (std::exception & /*e*/) {
|
||||||
|
GUI::show_error(plater().q,
|
||||||
|
L("Could not arrange model objects! "
|
||||||
|
"Some geometries may be invalid."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_status(count,
|
||||||
|
was_canceled() ? _(L("Arranging canceled."))
|
||||||
|
: _(L("Arranging done.")));
|
||||||
|
|
||||||
// it remains to move the wipe tower:
|
// it remains to move the wipe tower:
|
||||||
view3D->get_canvas3d()->arrange_wipe_tower(wti);
|
view3D->get_canvas3d()->arrange_wipe_tower(wti);
|
||||||
|
|
||||||
statusfn(0, L("Arranging done."));
|
|
||||||
statusbar()->set_range(prev_range);
|
|
||||||
statusbar()->set_cancel_callback(); // remove cancel button
|
|
||||||
|
|
||||||
// Do a full refresh of scene tree, including regenerating all the GLVolumes.
|
|
||||||
//FIXME The update function shall just reload the modified matrices.
|
|
||||||
update(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method will find an optimal orientation for the currently selected item
|
void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process()
|
||||||
// Very similar in nature to the arrange method above...
|
{
|
||||||
void Plater::priv::sla_optimize_rotation() {
|
int obj_idx = plater().get_selected_object_idx();
|
||||||
|
|
||||||
// TODO: we should decide whether to allow arrange when the search is
|
|
||||||
// running we should probably disable explicit slicing and background
|
|
||||||
// processing
|
|
||||||
|
|
||||||
if (rotoptimizing) { return; }
|
|
||||||
rotoptimizing = true;
|
|
||||||
Slic3r::ScopeGuard rotoptimizing_guard([this]() { rotoptimizing = false; });
|
|
||||||
|
|
||||||
int obj_idx = get_selected_object_idx();
|
|
||||||
if (obj_idx < 0) { return; }
|
if (obj_idx < 0) { return; }
|
||||||
|
|
||||||
ModelObject * o = model.objects[size_t(obj_idx)];
|
ModelObject *o = plater().model.objects[size_t(obj_idx)];
|
||||||
|
|
||||||
background_process.stop();
|
|
||||||
|
|
||||||
auto prev_range = statusbar()->get_range();
|
|
||||||
statusbar()->set_range(100);
|
|
||||||
|
|
||||||
auto stfn = [this] (unsigned st, const std::string& msg) {
|
|
||||||
statusbar()->set_progress(int(st));
|
|
||||||
statusbar()->set_status_text(msg);
|
|
||||||
|
|
||||||
// could be problematic, but we need the cancel button.
|
|
||||||
wxYieldIfNeeded();
|
|
||||||
};
|
|
||||||
|
|
||||||
statusbar()->set_cancel_callback([this, stfn](){
|
|
||||||
rotoptimizing = false;
|
|
||||||
stfn(0, L("Orientation search canceled"));
|
|
||||||
});
|
|
||||||
|
|
||||||
auto r = sla::find_best_rotation(
|
auto r = sla::find_best_rotation(
|
||||||
*o, .005f,
|
*o,
|
||||||
[stfn](unsigned s) { stfn(s, L("Searching for optimal orientation")); },
|
.005f,
|
||||||
[this](){ return !rotoptimizing; }
|
[this](unsigned s) {
|
||||||
);
|
if (s < 100)
|
||||||
|
update_status(int(s),
|
||||||
|
_(L("Searching for optimal orientation")));
|
||||||
|
},
|
||||||
|
[this]() { return was_canceled(); });
|
||||||
|
|
||||||
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
|
const auto *bed_shape_opt = plater().config->opt<ConfigOptionPoints>(
|
||||||
|
"bed_shape");
|
||||||
assert(bed_shape_opt);
|
assert(bed_shape_opt);
|
||||||
|
|
||||||
auto& bedpoints = bed_shape_opt->values;
|
auto & bedpoints = bed_shape_opt->values;
|
||||||
Polyline bed; bed.points.reserve(bedpoints.size());
|
Polyline bed;
|
||||||
for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
bed.points.reserve(bedpoints.size());
|
||||||
|
for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
||||||
|
|
||||||
double mindist = 6.0; // FIXME
|
double mindist = 6.0; // FIXME
|
||||||
double offs = mindist / 2.0 - EPSILON;
|
double offs = mindist / 2.0 - EPSILON;
|
||||||
|
|
||||||
if(rotoptimizing) // wasn't canceled
|
if (!was_canceled()) // wasn't canceled
|
||||||
for(ModelInstance * oi : o->instances) {
|
for (ModelInstance *oi : o->instances) {
|
||||||
oi->set_rotation({r[X], r[Y], r[Z]});
|
oi->set_rotation({r[X], r[Y], r[Z]});
|
||||||
|
|
||||||
auto trchull = o->convex_hull_2d(oi->get_transformation().get_matrix());
|
auto trchull = o->convex_hull_2d(
|
||||||
|
oi->get_transformation().get_matrix());
|
||||||
|
|
||||||
namespace opt = libnest2d::opt;
|
namespace opt = libnest2d::opt;
|
||||||
opt::StopCriteria stopcr;
|
opt::StopCriteria stopcr;
|
||||||
stopcr.relative_score_difference = 0.01;
|
stopcr.relative_score_difference = 0.01;
|
||||||
stopcr.max_iterations = 10000;
|
stopcr.max_iterations = 10000;
|
||||||
stopcr.stop_score = 0.0;
|
stopcr.stop_score = 0.0;
|
||||||
opt::GeneticOptimizer solver(stopcr);
|
opt::GeneticOptimizer solver(stopcr);
|
||||||
Polygon pbed(bed);
|
Polygon pbed(bed);
|
||||||
|
|
||||||
auto bin = pbed.bounding_box();
|
auto bin = pbed.bounding_box();
|
||||||
double binw = bin.size()(X) * SCALING_FACTOR - offs;
|
double binw = bin.size()(X) * SCALING_FACTOR - offs;
|
||||||
double binh = bin.size()(Y) * SCALING_FACTOR - offs;
|
double binh = bin.size()(Y) * SCALING_FACTOR - offs;
|
||||||
|
|
||||||
auto result = solver.optimize_min([&trchull, binw, binh](double rot){
|
auto result = solver.optimize_min(
|
||||||
auto chull = trchull;
|
[&trchull, binw, binh](double rot) {
|
||||||
chull.rotate(rot);
|
auto chull = trchull;
|
||||||
|
chull.rotate(rot);
|
||||||
|
|
||||||
auto bb = chull.bounding_box();
|
auto bb = chull.bounding_box();
|
||||||
double bbw = bb.size()(X) * SCALING_FACTOR;
|
double bbw = bb.size()(X) * SCALING_FACTOR;
|
||||||
double bbh = bb.size()(Y) * SCALING_FACTOR;
|
double bbh = bb.size()(Y) * SCALING_FACTOR;
|
||||||
|
|
||||||
auto wdiff = bbw - binw;
|
auto wdiff = bbw - binw;
|
||||||
auto hdiff = bbh - binh;
|
auto hdiff = bbh - binh;
|
||||||
double diff = 0;
|
double diff = 0;
|
||||||
if(wdiff < 0 && hdiff < 0) diff = wdiff + hdiff;
|
if (wdiff < 0 && hdiff < 0) diff = wdiff + hdiff;
|
||||||
if(wdiff > 0) diff += wdiff;
|
if (wdiff > 0) diff += wdiff;
|
||||||
if(hdiff > 0) diff += hdiff;
|
if (hdiff > 0) diff += hdiff;
|
||||||
|
|
||||||
return diff;
|
return diff;
|
||||||
}, opt::initvals(0.0), opt::bound(-PI/2, PI/2));
|
},
|
||||||
|
opt::initvals(0.0),
|
||||||
|
opt::bound(-PI / 2, PI / 2));
|
||||||
|
|
||||||
double r = std::get<0>(result.optimum);
|
double r = std::get<0>(result.optimum);
|
||||||
|
|
||||||
Vec3d rt = oi->get_rotation(); rt(Z) += r;
|
Vec3d rt = oi->get_rotation();
|
||||||
oi->set_rotation(rt);
|
rt(Z) += r;
|
||||||
|
oi->set_rotation(rt);
|
||||||
|
|
||||||
|
arr::WipeTowerInfo wti; // useless in SLA context
|
||||||
|
arr::find_new_position(plater().model,
|
||||||
|
o->instances,
|
||||||
|
coord_t(mindist / SCALING_FACTOR),
|
||||||
|
bed,
|
||||||
|
wti);
|
||||||
|
|
||||||
|
// Correct the z offset of the object which was corrupted be
|
||||||
|
// the rotation
|
||||||
|
o->ensure_on_bed();
|
||||||
|
|
||||||
|
update_status(100, _(L("Orientation found.")));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
update_status(100, _(L("Orientation search canceled.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
arr::WipeTowerInfo wti; // useless in SLA context
|
|
||||||
arr::find_new_position(model, o->instances, coord_t(mindist/SCALING_FACTOR), bed, wti);
|
|
||||||
|
|
||||||
// Correct the z offset of the object which was corrupted be the rotation
|
|
||||||
o->ensure_on_bed();
|
|
||||||
|
|
||||||
stfn(0, L("Orientation found."));
|
|
||||||
statusbar()->set_range(prev_range);
|
|
||||||
statusbar()->set_cancel_callback();
|
|
||||||
|
|
||||||
update(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Plater::priv::split_object()
|
void Plater::priv::split_object()
|
||||||
|
@ -2513,7 +2697,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation)
|
||||||
// Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState.
|
// Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState.
|
||||||
bool Plater::priv::restart_background_process(unsigned int state)
|
bool Plater::priv::restart_background_process(unsigned int state)
|
||||||
{
|
{
|
||||||
if (arranging || rotoptimizing) {
|
if (m_ui_jobs.is_any_running()) {
|
||||||
// Avoid a race condition
|
// Avoid a race condition
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2744,7 +2928,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
|
||||||
void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
|
void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
|
||||||
{
|
{
|
||||||
if (evt.status.percent >= -1) {
|
if (evt.status.percent >= -1) {
|
||||||
if (arranging || rotoptimizing) {
|
if (m_ui_jobs.is_any_running()) {
|
||||||
// Avoid a race condition
|
// Avoid a race condition
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -3225,7 +3409,7 @@ bool Plater::priv::can_fix_through_netfabb() const
|
||||||
|
|
||||||
bool Plater::priv::can_increase_instances() const
|
bool Plater::priv::can_increase_instances() const
|
||||||
{
|
{
|
||||||
if (arranging || rotoptimizing) {
|
if (m_ui_jobs.is_any_running()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3235,7 +3419,7 @@ bool Plater::priv::can_increase_instances() const
|
||||||
|
|
||||||
bool Plater::priv::can_decrease_instances() const
|
bool Plater::priv::can_decrease_instances() const
|
||||||
{
|
{
|
||||||
if (arranging || rotoptimizing) {
|
if (m_ui_jobs.is_any_running()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3255,7 +3439,7 @@ bool Plater::priv::can_split_to_volumes() const
|
||||||
|
|
||||||
bool Plater::priv::can_arrange() const
|
bool Plater::priv::can_arrange() const
|
||||||
{
|
{
|
||||||
return !model.objects.empty() && !arranging;
|
return !model.objects.empty() && !m_ui_jobs.is_any_running();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Plater::priv::can_layers_editing() const
|
bool Plater::priv::can_layers_editing() const
|
||||||
|
@ -3383,6 +3567,8 @@ void Plater::load_files(const std::vector<std::string>& input_files, bool load_m
|
||||||
|
|
||||||
void Plater::update() { p->update(); }
|
void Plater::update() { p->update(); }
|
||||||
|
|
||||||
|
void Plater::stop_jobs() { p->m_ui_jobs.stop_all(); }
|
||||||
|
|
||||||
void Plater::update_ui_from_settings() { p->update_ui_from_settings(); }
|
void Plater::update_ui_from_settings() { p->update_ui_from_settings(); }
|
||||||
|
|
||||||
void Plater::select_view(const std::string& direction) { p->select_view(direction); }
|
void Plater::select_view(const std::string& direction) { p->select_view(direction); }
|
||||||
|
@ -3689,7 +3875,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
|
||||||
if (!path.Lower().EndsWith(".3mf"))
|
if (!path.Lower().EndsWith(".3mf"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
|
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
|
||||||
const std::string path_u8 = into_u8(path);
|
const std::string path_u8 = into_u8(path);
|
||||||
wxBusyCursor wait;
|
wxBusyCursor wait;
|
||||||
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) {
|
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) {
|
||||||
|
@ -3705,6 +3891,9 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
|
||||||
|
|
||||||
void Plater::reslice()
|
void Plater::reslice()
|
||||||
{
|
{
|
||||||
|
// Stop arrange and (or) optimize rotation tasks.
|
||||||
|
this->stop_jobs();
|
||||||
|
|
||||||
//FIXME Don't reslice if export of G-code or sending to OctoPrint is running.
|
//FIXME Don't reslice if export of G-code or sending to OctoPrint is running.
|
||||||
// bitmask of UpdateBackgroundProcessReturnState
|
// bitmask of UpdateBackgroundProcessReturnState
|
||||||
unsigned int state = this->p->update_background_process(true);
|
unsigned int state = this->p->update_background_process(true);
|
||||||
|
@ -3740,7 +3929,7 @@ void Plater::reslice_SLA_supports(const ModelObject &object)
|
||||||
if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
|
if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
|
||||||
this->p->view3D->reload_scene(false);
|
this->p->view3D->reload_scene(false);
|
||||||
|
|
||||||
if (this->p->background_process.empty() || (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID))
|
if (this->p->background_process.empty() || (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID))
|
||||||
// Nothing to do on empty input or invalid configuration.
|
// Nothing to do on empty input or invalid configuration.
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -144,6 +144,7 @@ public:
|
||||||
void load_files(const std::vector<std::string>& input_files, bool load_model = true, bool load_config = true);
|
void load_files(const std::vector<std::string>& input_files, bool load_model = true, bool load_config = true);
|
||||||
|
|
||||||
void update();
|
void update();
|
||||||
|
void stop_jobs();
|
||||||
void select_view(const std::string& direction);
|
void select_view(const std::string& direction);
|
||||||
void select_view_3D(const std::string& name);
|
void select_view_3D(const std::string& name);
|
||||||
|
|
||||||
|
|
|
@ -168,6 +168,11 @@ void ProgressStatusBar::set_status_text(const char *txt)
|
||||||
this->set_status_text(wxString::FromUTF8(txt));
|
this->set_status_text(wxString::FromUTF8(txt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wxString ProgressStatusBar::get_status_text() const
|
||||||
|
{
|
||||||
|
return self->GetStatusText();
|
||||||
|
}
|
||||||
|
|
||||||
void ProgressStatusBar::show_cancel_button()
|
void ProgressStatusBar::show_cancel_button()
|
||||||
{
|
{
|
||||||
if(m_cancelbutton) m_cancelbutton->Show();
|
if(m_cancelbutton) m_cancelbutton->Show();
|
||||||
|
|
|
@ -52,6 +52,7 @@ public:
|
||||||
void set_status_text(const wxString& txt);
|
void set_status_text(const wxString& txt);
|
||||||
void set_status_text(const std::string& txt);
|
void set_status_text(const std::string& txt);
|
||||||
void set_status_text(const char *txt);
|
void set_status_text(const char *txt);
|
||||||
|
wxString get_status_text() const;
|
||||||
|
|
||||||
// Temporary methods to satisfy Perl side
|
// Temporary methods to satisfy Perl side
|
||||||
void show_cancel_button();
|
void show_cancel_button();
|
||||||
|
|
Loading…
Reference in a new issue