mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-05-12 19:38:09 +08:00
Separate jobs from Plater, re-add big bed workaround
This commit is contained in:
parent
1bffc2b99b
commit
728d90cb33
@ -27,7 +27,7 @@ bool apply_arrange_polys(ArrangePolygons &input, ModelInstancePtrs &instances, V
|
|||||||
for(size_t i = 0; i < input.size(); ++i) {
|
for(size_t i = 0; i < input.size(); ++i) {
|
||||||
if (input[i].bed_idx != 0) { ret = false; if (vfn) vfn(input[i]); }
|
if (input[i].bed_idx != 0) { ret = false; if (vfn) vfn(input[i]); }
|
||||||
if (input[i].bed_idx >= 0)
|
if (input[i].bed_idx >= 0)
|
||||||
instances[i]->apply_arrange_result(input[i].translation,
|
instances[i]->apply_arrange_result(input[i].translation.cast<double>(),
|
||||||
input[i].rotation);
|
input[i].rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +148,11 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
GUI/PrintHostDialogs.cpp
|
GUI/PrintHostDialogs.cpp
|
||||||
GUI/PrintHostDialogs.hpp
|
GUI/PrintHostDialogs.hpp
|
||||||
GUI/Job.hpp
|
GUI/Job.hpp
|
||||||
|
GUI/Job.cpp
|
||||||
|
GUI/ArrangeJob.hpp
|
||||||
|
GUI/ArrangeJob.cpp
|
||||||
|
GUI/RotoptimizeJob.hpp
|
||||||
|
GUI/RotoptimizeJob.cpp
|
||||||
GUI/Mouse3DController.cpp
|
GUI/Mouse3DController.cpp
|
||||||
GUI/Mouse3DController.hpp
|
GUI/Mouse3DController.hpp
|
||||||
GUI/DoubleSlider.cpp
|
GUI/DoubleSlider.cpp
|
||||||
|
223
src/slic3r/GUI/ArrangeJob.cpp
Normal file
223
src/slic3r/GUI/ArrangeJob.cpp
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
#include "ArrangeJob.hpp"
|
||||||
|
|
||||||
|
#include "libslic3r/MTUtils.hpp"
|
||||||
|
|
||||||
|
#include "Plater.hpp"
|
||||||
|
#include "GLCanvas3D.hpp"
|
||||||
|
#include "GUI.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r { namespace GUI {
|
||||||
|
|
||||||
|
// Cache the wti info
|
||||||
|
class WipeTower: public GLCanvas3D::WipeTowerInfo {
|
||||||
|
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||||
|
public:
|
||||||
|
explicit WipeTower(const GLCanvas3D::WipeTowerInfo &wti)
|
||||||
|
: GLCanvas3D::WipeTowerInfo(wti)
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit WipeTower(GLCanvas3D::WipeTowerInfo &&wti)
|
||||||
|
: GLCanvas3D::WipeTowerInfo(std::move(wti))
|
||||||
|
{}
|
||||||
|
|
||||||
|
void apply_arrange_result(const Vec2d& tr, double rotation)
|
||||||
|
{
|
||||||
|
m_pos = unscaled(tr); m_rotation = rotation;
|
||||||
|
apply_wipe_tower();
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrangePolygon get_arrange_polygon() const
|
||||||
|
{
|
||||||
|
Polygon ap({
|
||||||
|
{coord_t(0), coord_t(0)},
|
||||||
|
{scaled(m_bb_size(X)), coord_t(0)},
|
||||||
|
{scaled(m_bb_size)},
|
||||||
|
{coord_t(0), scaled(m_bb_size(Y))},
|
||||||
|
{coord_t(0), coord_t(0)},
|
||||||
|
});
|
||||||
|
|
||||||
|
ArrangePolygon ret;
|
||||||
|
ret.poly.contour = std::move(ap);
|
||||||
|
ret.translation = scaled(m_pos);
|
||||||
|
ret.rotation = m_rotation;
|
||||||
|
ret.priority++;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static WipeTower get_wipe_tower(Plater &plater)
|
||||||
|
{
|
||||||
|
return WipeTower{plater.canvas3D()->get_wipe_tower_info()};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArrangeJob::clear_input()
|
||||||
|
{
|
||||||
|
const Model &model = m_plater->model();
|
||||||
|
|
||||||
|
size_t count = 0, cunprint = 0; // To know how much space to reserve
|
||||||
|
for (auto obj : model.objects)
|
||||||
|
for (auto mi : obj->instances)
|
||||||
|
mi->printable ? count++ : cunprint++;
|
||||||
|
|
||||||
|
m_selected.clear();
|
||||||
|
m_unselected.clear();
|
||||||
|
m_unprintable.clear();
|
||||||
|
m_selected.reserve(count + 1 /* for optional wti */);
|
||||||
|
m_unselected.reserve(count + 1 /* for optional wti */);
|
||||||
|
m_unprintable.reserve(cunprint /* for optional wti */);
|
||||||
|
}
|
||||||
|
|
||||||
|
double ArrangeJob::bed_stride() const {
|
||||||
|
double bedwidth = m_plater->bed_shape_bb().size().x();
|
||||||
|
return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArrangeJob::prepare_all() {
|
||||||
|
clear_input();
|
||||||
|
|
||||||
|
for (ModelObject *obj: m_plater->model().objects)
|
||||||
|
for (ModelInstance *mi : obj->instances) {
|
||||||
|
ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
|
||||||
|
cont.emplace_back(get_arrange_poly(mi));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto wti = get_wipe_tower(*m_plater))
|
||||||
|
m_selected.emplace_back(wti.get_arrange_polygon());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArrangeJob::prepare_selected() {
|
||||||
|
clear_input();
|
||||||
|
|
||||||
|
Model &model = m_plater->model();
|
||||||
|
double stride = bed_stride();
|
||||||
|
|
||||||
|
std::vector<const Selection::InstanceIdxsList *>
|
||||||
|
obj_sel(model.objects.size(), nullptr);
|
||||||
|
|
||||||
|
for (auto &s : m_plater->get_selection().get_content())
|
||||||
|
if (s.first < int(obj_sel.size()))
|
||||||
|
obj_sel[size_t(s.first)] = &s.second;
|
||||||
|
|
||||||
|
// Go through the objects and check if inside the selection
|
||||||
|
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
|
||||||
|
const Selection::InstanceIdxsList * instlist = obj_sel[oidx];
|
||||||
|
ModelObject *mo = model.objects[oidx];
|
||||||
|
|
||||||
|
std::vector<bool> inst_sel(mo->instances.size(), false);
|
||||||
|
|
||||||
|
if (instlist)
|
||||||
|
for (auto inst_id : *instlist)
|
||||||
|
inst_sel[size_t(inst_id)] = true;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < inst_sel.size(); ++i) {
|
||||||
|
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
|
||||||
|
|
||||||
|
ArrangePolygons &cont = mo->instances[i]->printable ?
|
||||||
|
(inst_sel[i] ? m_selected :
|
||||||
|
m_unselected) :
|
||||||
|
m_unprintable;
|
||||||
|
|
||||||
|
cont.emplace_back(std::move(ap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto wti = get_wipe_tower(*m_plater)) {
|
||||||
|
ArrangePolygon &&ap = get_arrange_poly(&wti);
|
||||||
|
|
||||||
|
m_plater->get_selection().is_wipe_tower() ?
|
||||||
|
m_selected.emplace_back(std::move(ap)) :
|
||||||
|
m_unselected.emplace_back(std::move(ap));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the selection was empty arrange everything
|
||||||
|
if (m_selected.empty()) m_selected.swap(m_unselected);
|
||||||
|
|
||||||
|
// The strides have to be removed from the fixed items. For the
|
||||||
|
// arrangeable (selected) items bed_idx is ignored and the
|
||||||
|
// translation is irrelevant.
|
||||||
|
for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArrangeJob::prepare()
|
||||||
|
{
|
||||||
|
wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArrangeJob::process()
|
||||||
|
{
|
||||||
|
static const auto arrangestr = _(L("Arranging"));
|
||||||
|
|
||||||
|
double dist = min_object_distance(*m_plater->config());
|
||||||
|
|
||||||
|
arrangement::ArrangeParams params;
|
||||||
|
params.min_obj_distance = scaled(dist);
|
||||||
|
|
||||||
|
auto count = unsigned(m_selected.size() + m_unprintable.size());
|
||||||
|
Points bedpts = get_bed_shape(*m_plater->config());
|
||||||
|
|
||||||
|
params.stopcondition = [this]() { return was_canceled(); };
|
||||||
|
|
||||||
|
try {
|
||||||
|
params.progressind = [this, count](unsigned st) {
|
||||||
|
st += m_unprintable.size();
|
||||||
|
if (st > 0) update_status(int(count - st), arrangestr);
|
||||||
|
};
|
||||||
|
|
||||||
|
arrangement::arrange(m_selected, m_unselected, bedpts, params);
|
||||||
|
|
||||||
|
params.progressind = [this, count](unsigned st) {
|
||||||
|
if (st > 0) update_status(int(count - st), arrangestr);
|
||||||
|
};
|
||||||
|
|
||||||
|
arrangement::arrange(m_unprintable, {}, bedpts, params);
|
||||||
|
} catch (std::exception & /*e*/) {
|
||||||
|
GUI::show_error(m_plater,
|
||||||
|
_(L("Could not arrange model objects! "
|
||||||
|
"Some geometries may be invalid.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalize just here.
|
||||||
|
update_status(int(count),
|
||||||
|
was_canceled() ? _(L("Arranging canceled."))
|
||||||
|
: _(L("Arranging done.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArrangeJob::finalize() {
|
||||||
|
// Ignore the arrange result if aborted.
|
||||||
|
if (was_canceled()) return;
|
||||||
|
|
||||||
|
// Unprintable items go to the last virtual bed
|
||||||
|
int beds = 0;
|
||||||
|
|
||||||
|
// Apply the arrange result to all selected objects
|
||||||
|
for (ArrangePolygon &ap : m_selected) {
|
||||||
|
beds = std::max(ap.bed_idx, beds);
|
||||||
|
ap.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the virtual beds from the unselected items
|
||||||
|
for (ArrangePolygon &ap : m_unselected)
|
||||||
|
beds = std::max(ap.bed_idx, beds);
|
||||||
|
|
||||||
|
// Move the unprintable items to the last virtual bed.
|
||||||
|
for (ArrangePolygon &ap : m_unprintable) {
|
||||||
|
ap.bed_idx += beds + 1;
|
||||||
|
ap.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_plater->update();
|
||||||
|
|
||||||
|
Job::finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &plater)
|
||||||
|
{
|
||||||
|
return WipeTower{plater.canvas3D()->get_wipe_tower_info()}.get_arrange_polygon();
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap)
|
||||||
|
{
|
||||||
|
WipeTower{plater.canvas3D()->get_wipe_tower_info()}.apply_arrange_result(ap.translation.cast<double>(), ap.rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace Slic3r::GUI
|
77
src/slic3r/GUI/ArrangeJob.hpp
Normal file
77
src/slic3r/GUI/ArrangeJob.hpp
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#ifndef ARRANGEJOB_HPP
|
||||||
|
#define ARRANGEJOB_HPP
|
||||||
|
|
||||||
|
#include "Job.hpp"
|
||||||
|
#include "libslic3r/Arrange.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r { namespace GUI {
|
||||||
|
|
||||||
|
class Plater;
|
||||||
|
|
||||||
|
class ArrangeJob : public Job
|
||||||
|
{
|
||||||
|
Plater *m_plater;
|
||||||
|
|
||||||
|
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||||
|
using ArrangePolygons = arrangement::ArrangePolygons;
|
||||||
|
|
||||||
|
// The gap between logical beds in the x axis expressed in ratio of
|
||||||
|
// the current bed width.
|
||||||
|
static const constexpr double LOGICAL_BED_GAP = 1. / 5.;
|
||||||
|
|
||||||
|
ArrangePolygons m_selected, m_unselected, m_unprintable;
|
||||||
|
|
||||||
|
// clear m_selected and m_unselected, reserve space for next usage
|
||||||
|
void clear_input();
|
||||||
|
|
||||||
|
// Stride between logical beds
|
||||||
|
double bed_stride() const;
|
||||||
|
|
||||||
|
// Set up arrange polygon for a ModelInstance and Wipe tower
|
||||||
|
template<class T> ArrangePolygon get_arrange_poly(T *obj) const
|
||||||
|
{
|
||||||
|
ArrangePolygon ap = obj->get_arrange_polygon();
|
||||||
|
ap.priority = 0;
|
||||||
|
ap.bed_idx = ap.translation.x() / bed_stride();
|
||||||
|
ap.setter = [obj, this](const ArrangePolygon &p) {
|
||||||
|
if (p.is_arranged()) {
|
||||||
|
Vec2d t = p.translation.cast<double>();
|
||||||
|
t.x() += p.bed_idx * bed_stride();
|
||||||
|
obj->apply_arrange_result(t, p.rotation);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return ap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare all objects on the bed regardless of the selection
|
||||||
|
void prepare_all();
|
||||||
|
|
||||||
|
// Prepare the selected and unselected items separately. If nothing is
|
||||||
|
// selected, behaves as if everything would be selected.
|
||||||
|
void prepare_selected();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void prepare() override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ArrangeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||||
|
: Job{std::move(pri)}, m_plater{plater}
|
||||||
|
{}
|
||||||
|
|
||||||
|
int status_range() const override
|
||||||
|
{
|
||||||
|
return int(m_selected.size() + m_unprintable.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void process() override;
|
||||||
|
|
||||||
|
void finalize() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &);
|
||||||
|
void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap);
|
||||||
|
|
||||||
|
}} // namespace Slic3r::GUI
|
||||||
|
|
||||||
|
#endif // ARRANGEJOB_HPP
|
121
src/slic3r/GUI/Job.cpp
Normal file
121
src/slic3r/GUI/Job.cpp
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "Job.hpp"
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
void GUI::Job::run()
|
||||||
|
{
|
||||||
|
m_running.store(true);
|
||||||
|
process();
|
||||||
|
m_running.store(false);
|
||||||
|
|
||||||
|
// ensure to call the last status to finalize the job
|
||||||
|
update_status(status_range(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUI::Job::update_status(int st, const wxString &msg)
|
||||||
|
{
|
||||||
|
auto evt = new wxThreadEvent();
|
||||||
|
evt->SetInt(st);
|
||||||
|
evt->SetString(msg);
|
||||||
|
wxQueueEvent(this, evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri)
|
||||||
|
: m_progress(std::move(pri))
|
||||||
|
{
|
||||||
|
Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
|
||||||
|
auto msg = evt.GetString();
|
||||||
|
if (!msg.empty())
|
||||||
|
m_progress->set_status_text(msg.ToUTF8().data());
|
||||||
|
|
||||||
|
if (m_finalized) return;
|
||||||
|
|
||||||
|
m_progress->set_progress(evt.GetInt());
|
||||||
|
if (evt.GetInt() == status_range()) {
|
||||||
|
// set back the original range and cancel callback
|
||||||
|
m_progress->set_range(m_range);
|
||||||
|
m_progress->set_cancel_callback();
|
||||||
|
wxEndBusyCursor();
|
||||||
|
|
||||||
|
finalize();
|
||||||
|
|
||||||
|
// dont do finalization again for the same process
|
||||||
|
m_finalized = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUI::Job::start()
|
||||||
|
{ // Start the job. No effect if the job is already running
|
||||||
|
if (!m_running.load()) {
|
||||||
|
prepare();
|
||||||
|
|
||||||
|
// Save the current status indicatior range and push the new one
|
||||||
|
m_range = m_progress->get_range();
|
||||||
|
m_progress->set_range(status_range());
|
||||||
|
|
||||||
|
// init cancellation flag and set the cancel callback
|
||||||
|
m_canceled.store(false);
|
||||||
|
m_progress->set_cancel_callback(
|
||||||
|
[this]() { m_canceled.store(true); });
|
||||||
|
|
||||||
|
m_finalized = false;
|
||||||
|
|
||||||
|
// Changing cursor to busy
|
||||||
|
wxBeginBusyCursor();
|
||||||
|
|
||||||
|
try { // Execute the job
|
||||||
|
m_thread = create_thread([this] { this->run(); });
|
||||||
|
} catch (std::exception &) {
|
||||||
|
update_status(status_range(),
|
||||||
|
_(L("ERROR: not enough resources to "
|
||||||
|
"execute a new job.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The state changes will be undone when the process hits the
|
||||||
|
// last status value, in the status update handler (see ctor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GUI::Job::join(int timeout_ms)
|
||||||
|
{
|
||||||
|
if (!m_thread.joinable()) return true;
|
||||||
|
|
||||||
|
if (timeout_ms <= 0)
|
||||||
|
m_thread.join();
|
||||||
|
else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUI::ExclusiveJobGroup::start(size_t jid) {
|
||||||
|
assert(jid < m_jobs.size());
|
||||||
|
stop_all();
|
||||||
|
m_jobs[jid]->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUI::ExclusiveJobGroup::join_all(int wait_ms)
|
||||||
|
{
|
||||||
|
std::vector<bool> aborted(m_jobs.size(), false);
|
||||||
|
|
||||||
|
for (size_t jid = 0; jid < m_jobs.size(); ++jid)
|
||||||
|
aborted[jid] = m_jobs[jid]->join(wait_ms);
|
||||||
|
|
||||||
|
if (!std::all_of(aborted.begin(), aborted.end(), [](bool t) { return t; }))
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Could not abort a job!";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GUI::ExclusiveJobGroup::is_any_running() const
|
||||||
|
{
|
||||||
|
return std::any_of(m_jobs.begin(), m_jobs.end(),
|
||||||
|
[](const std::unique_ptr<GUI::Job> &j) {
|
||||||
|
return j->is_running();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -31,30 +31,16 @@ class Job : public wxEvtHandler
|
|||||||
bool m_finalized = false;
|
bool m_finalized = false;
|
||||||
std::shared_ptr<ProgressIndicator> m_progress;
|
std::shared_ptr<ProgressIndicator> m_progress;
|
||||||
|
|
||||||
void run()
|
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:
|
protected:
|
||||||
// status range for a particular job
|
// status range for a particular job
|
||||||
virtual int status_range() const { return 100; }
|
virtual int status_range() const { return 100; }
|
||||||
|
|
||||||
// status update, to be used from the work thread (process() method)
|
// status update, to be used from the work thread (process() method)
|
||||||
void update_status(int st, const wxString &msg = "")
|
void update_status(int st, const wxString &msg = "");
|
||||||
{
|
|
||||||
auto evt = new wxThreadEvent();
|
|
||||||
evt->SetInt(st);
|
|
||||||
evt->SetString(msg);
|
|
||||||
wxQueueEvent(this, evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool was_canceled() const { return m_canceled.load(); }
|
bool was_canceled() const { return m_canceled.load(); }
|
||||||
|
|
||||||
// Launched just before start(), a job can use it to prepare internals
|
// Launched just before start(), a job can use it to prepare internals
|
||||||
virtual void prepare() {}
|
virtual void prepare() {}
|
||||||
@ -62,31 +48,8 @@ protected:
|
|||||||
// Launched when the job is finished. It refreshes the 3Dscene by def.
|
// Launched when the job is finished. It refreshes the 3Dscene by def.
|
||||||
virtual void finalize() { m_finalized = true; }
|
virtual void finalize() { m_finalized = true; }
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Job(std::shared_ptr<ProgressIndicator> pri) : m_progress(pri)
|
Job(std::shared_ptr<ProgressIndicator> pri);
|
||||||
{
|
|
||||||
Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
|
|
||||||
auto msg = evt.GetString();
|
|
||||||
if (!msg.empty())
|
|
||||||
m_progress->set_status_text(msg.ToUTF8().data());
|
|
||||||
|
|
||||||
if (m_finalized) return;
|
|
||||||
|
|
||||||
m_progress->set_progress(evt.GetInt());
|
|
||||||
if (evt.GetInt() == status_range()) {
|
|
||||||
// set back the original range and cancel callback
|
|
||||||
m_progress->set_range(m_range);
|
|
||||||
m_progress->set_cancel_callback();
|
|
||||||
wxEndBusyCursor();
|
|
||||||
|
|
||||||
finalize();
|
|
||||||
|
|
||||||
// dont do finalization again for the same process
|
|
||||||
m_finalized = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_finalized() const { return m_finalized; }
|
bool is_finalized() const { return m_finalized; }
|
||||||
|
|
||||||
@ -97,59 +60,50 @@ public:
|
|||||||
|
|
||||||
virtual void process() = 0;
|
virtual void process() = 0;
|
||||||
|
|
||||||
void start()
|
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 = m_progress->get_range();
|
|
||||||
m_progress->set_range(status_range());
|
|
||||||
|
|
||||||
// init cancellation flag and set the cancel callback
|
|
||||||
m_canceled.store(false);
|
|
||||||
m_progress->set_cancel_callback(
|
|
||||||
[this]() { m_canceled.store(true); });
|
|
||||||
|
|
||||||
m_finalized = false;
|
|
||||||
|
|
||||||
// Changing cursor to busy
|
|
||||||
wxBeginBusyCursor();
|
|
||||||
|
|
||||||
try { // Execute the job
|
|
||||||
m_thread = create_thread([this] { this->run(); });
|
|
||||||
} catch (std::exception &) {
|
|
||||||
update_status(status_range(),
|
|
||||||
_(L("ERROR: not enough resources to "
|
|
||||||
"execute a new job.")));
|
|
||||||
}
|
|
||||||
|
|
||||||
// The state changes will be undone when the process hits the
|
|
||||||
// last status value, in the status update handler (see ctor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// To wait for the running job and join the threads. False is
|
// To wait for the running job and join the threads. False is
|
||||||
// returned if the timeout has been reached and the job is still
|
// returned if the timeout has been reached and the job is still
|
||||||
// running. Call cancel() before this fn if you want to explicitly
|
// running. Call cancel() before this fn if you want to explicitly
|
||||||
// end the job.
|
// end the job.
|
||||||
bool join(int timeout_ms = 0)
|
bool join(int timeout_ms = 0);
|
||||||
{
|
|
||||||
if (!m_thread.joinable()) return true;
|
|
||||||
|
|
||||||
if (timeout_ms <= 0)
|
|
||||||
m_thread.join();
|
|
||||||
else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_running() const { return m_running.load(); }
|
bool is_running() const { return m_running.load(); }
|
||||||
void cancel() { m_canceled.store(true); }
|
void cancel() { m_canceled.store(true); }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
// Jobs defined inside the group class will be managed so that only one can
|
||||||
}
|
// run at a time. Also, the background process will be stopped if a job is
|
||||||
|
// started.
|
||||||
|
class ExclusiveJobGroup
|
||||||
|
{
|
||||||
|
static const int ABORT_WAIT_MAX_MS = 10000;
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<GUI::Job>> m_jobs;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void before_start() {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~ExclusiveJobGroup() = default;
|
||||||
|
|
||||||
|
size_t add_job(std::unique_ptr<GUI::Job> &&job)
|
||||||
|
{
|
||||||
|
m_jobs.emplace_back(std::move(job));
|
||||||
|
return m_jobs.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void start(size_t jid);
|
||||||
|
|
||||||
|
void cancel_all() { for (auto& j : m_jobs) j->cancel(); }
|
||||||
|
|
||||||
|
void join_all(int wait_ms = 0);
|
||||||
|
|
||||||
|
void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); }
|
||||||
|
|
||||||
|
bool is_any_running() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespace Slic3r::GUI
|
||||||
|
|
||||||
#endif // JOB_HPP
|
#endif // JOB_HPP
|
||||||
|
@ -36,7 +36,6 @@
|
|||||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
#include "libslic3r/SLA/Hollowing.hpp"
|
#include "libslic3r/SLA/Hollowing.hpp"
|
||||||
#include "libslic3r/SLA/Rotfinder.hpp"
|
|
||||||
#include "libslic3r/SLA/SupportPoint.hpp"
|
#include "libslic3r/SLA/SupportPoint.hpp"
|
||||||
#include "libslic3r/Polygon.hpp"
|
#include "libslic3r/Polygon.hpp"
|
||||||
#include "libslic3r/Print.hpp"
|
#include "libslic3r/Print.hpp"
|
||||||
@ -44,13 +43,6 @@
|
|||||||
#include "libslic3r/SLAPrint.hpp"
|
#include "libslic3r/SLAPrint.hpp"
|
||||||
#include "libslic3r/Utils.hpp"
|
#include "libslic3r/Utils.hpp"
|
||||||
|
|
||||||
//#include "libslic3r/ClipperUtils.hpp"
|
|
||||||
|
|
||||||
// #include "libnest2d/optimizers/nlopt/genetic.hpp"
|
|
||||||
// #include "libnest2d/backends/clipper/geometries.hpp"
|
|
||||||
// #include "libnest2d/utils/rotcalipers.hpp"
|
|
||||||
#include "libslic3r/MinAreaBoundingBox.hpp"
|
|
||||||
|
|
||||||
#include "GUI.hpp"
|
#include "GUI.hpp"
|
||||||
#include "GUI_App.hpp"
|
#include "GUI_App.hpp"
|
||||||
#include "GUI_ObjectList.hpp"
|
#include "GUI_ObjectList.hpp"
|
||||||
@ -69,7 +61,8 @@
|
|||||||
#include "Camera.hpp"
|
#include "Camera.hpp"
|
||||||
#include "Mouse3DController.hpp"
|
#include "Mouse3DController.hpp"
|
||||||
#include "Tab.hpp"
|
#include "Tab.hpp"
|
||||||
#include "Job.hpp"
|
#include "ArrangeJob.hpp"
|
||||||
|
#include "RotoptimizeJob.hpp"
|
||||||
#include "PresetBundle.hpp"
|
#include "PresetBundle.hpp"
|
||||||
#include "BackgroundSlicingProcess.hpp"
|
#include "BackgroundSlicingProcess.hpp"
|
||||||
#include "ProgressStatusBar.hpp"
|
#include "ProgressStatusBar.hpp"
|
||||||
@ -1485,311 +1478,37 @@ struct Plater::priv
|
|||||||
BackgroundSlicingProcess background_process;
|
BackgroundSlicingProcess background_process;
|
||||||
bool suppressed_backround_processing_update { false };
|
bool suppressed_backround_processing_update { false };
|
||||||
|
|
||||||
// Cache the wti info
|
|
||||||
class WipeTower: public GLCanvas3D::WipeTowerInfo {
|
|
||||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
|
||||||
friend priv;
|
|
||||||
public:
|
|
||||||
|
|
||||||
void apply_arrange_result(const Vec2d& tr, double rotation)
|
|
||||||
{
|
|
||||||
m_pos = unscaled(tr); m_rotation = rotation;
|
|
||||||
apply_wipe_tower();
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrangePolygon get_arrange_polygon() const
|
|
||||||
{
|
|
||||||
Polygon p({
|
|
||||||
{coord_t(0), coord_t(0)},
|
|
||||||
{scaled(m_bb_size(X)), coord_t(0)},
|
|
||||||
{scaled(m_bb_size)},
|
|
||||||
{coord_t(0), scaled(m_bb_size(Y))},
|
|
||||||
{coord_t(0), coord_t(0)},
|
|
||||||
});
|
|
||||||
|
|
||||||
ArrangePolygon ret;
|
|
||||||
ret.poly.contour = std::move(p);
|
|
||||||
ret.translation = scaled(m_pos);
|
|
||||||
ret.rotation = m_rotation;
|
|
||||||
ret.priority++;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
} wipetower;
|
|
||||||
|
|
||||||
WipeTower& updated_wipe_tower() {
|
|
||||||
auto wti = view3D->get_canvas3d()->get_wipe_tower_info();
|
|
||||||
wipetower.m_pos = wti.pos();
|
|
||||||
wipetower.m_rotation = wti.rotation();
|
|
||||||
wipetower.m_bb_size = wti.bb_size();
|
|
||||||
return wipetower;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 PlaterJob: public Job
|
|
||||||
{
|
|
||||||
priv *m_plater;
|
|
||||||
protected:
|
|
||||||
|
|
||||||
priv & plater() { return *m_plater; }
|
|
||||||
const priv &plater() const { return *m_plater; }
|
|
||||||
|
|
||||||
// Launched when the job is finished. It refreshes the 3Dscene by def.
|
|
||||||
void finalize() override
|
|
||||||
{
|
|
||||||
// Do a full refresh of scene tree, including regenerating
|
|
||||||
// all the GLVolumes. FIXME The update function shall just
|
|
||||||
// reload the modified matrices.
|
|
||||||
if (!Job::was_canceled())
|
|
||||||
plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
|
|
||||||
|
|
||||||
Job::finalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
PlaterJob(priv *_plater)
|
|
||||||
: Job(_plater->statusbar()), m_plater(_plater)
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Jobs : size_t {
|
|
||||||
Arrange,
|
|
||||||
Rotoptimize
|
|
||||||
};
|
|
||||||
|
|
||||||
class ArrangeJob : public PlaterJob
|
|
||||||
{
|
|
||||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
|
||||||
using ArrangePolygons = arrangement::ArrangePolygons;
|
|
||||||
|
|
||||||
// The gap between logical beds in the x axis expressed in ratio of
|
|
||||||
// the current bed width.
|
|
||||||
static const constexpr double LOGICAL_BED_GAP = 1. / 5.;
|
|
||||||
|
|
||||||
ArrangePolygons m_selected, m_unselected, m_unprintable;
|
|
||||||
|
|
||||||
// clear m_selected and m_unselected, reserve space for next usage
|
|
||||||
void clear_input() {
|
|
||||||
const Model &model = plater().model;
|
|
||||||
|
|
||||||
size_t count = 0, cunprint = 0; // To know how much space to reserve
|
|
||||||
for (auto obj : model.objects)
|
|
||||||
for (auto mi : obj->instances)
|
|
||||||
mi->printable ? count++ : cunprint++;
|
|
||||||
|
|
||||||
m_selected.clear();
|
|
||||||
m_unselected.clear();
|
|
||||||
m_unprintable.clear();
|
|
||||||
m_selected.reserve(count + 1 /* for optional wti */);
|
|
||||||
m_unselected.reserve(count + 1 /* for optional wti */);
|
|
||||||
m_unprintable.reserve(cunprint /* for optional wti */);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stride between logical beds
|
|
||||||
double bed_stride() const {
|
|
||||||
double bedwidth = plater().bed_shape_bb().size().x();
|
|
||||||
return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up arrange polygon for a ModelInstance and Wipe tower
|
|
||||||
template<class T> ArrangePolygon get_arrange_poly(T *obj) const {
|
|
||||||
ArrangePolygon ap = obj->get_arrange_polygon();
|
|
||||||
ap.priority = 0;
|
|
||||||
ap.bed_idx = ap.translation.x() / bed_stride();
|
|
||||||
ap.setter = [obj, this](const ArrangePolygon &p) {
|
|
||||||
if (p.is_arranged()) {
|
|
||||||
Vec2d t = p.translation.cast<double>();
|
|
||||||
t.x() += p.bed_idx * bed_stride();
|
|
||||||
obj->apply_arrange_result(t, p.rotation);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return ap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare all objects on the bed regardless of the selection
|
|
||||||
void prepare_all() {
|
|
||||||
clear_input();
|
|
||||||
|
|
||||||
for (ModelObject *obj: plater().model.objects)
|
|
||||||
for (ModelInstance *mi : obj->instances) {
|
|
||||||
ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
|
|
||||||
cont.emplace_back(get_arrange_poly(mi));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& wti = plater().updated_wipe_tower();
|
|
||||||
if (wti) m_selected.emplace_back(get_arrange_poly(&wti));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the selected and unselected items separately. If nothing is
|
|
||||||
// selected, behaves as if everything would be selected.
|
|
||||||
void prepare_selected() {
|
|
||||||
clear_input();
|
|
||||||
|
|
||||||
Model &model = plater().model;
|
|
||||||
coord_t stride = bed_stride();
|
|
||||||
|
|
||||||
std::vector<const Selection::InstanceIdxsList *>
|
|
||||||
obj_sel(model.objects.size(), nullptr);
|
|
||||||
|
|
||||||
for (auto &s : plater().get_selection().get_content())
|
|
||||||
if (s.first < int(obj_sel.size()))
|
|
||||||
obj_sel[size_t(s.first)] = &s.second;
|
|
||||||
|
|
||||||
// Go through the objects and check if inside the selection
|
|
||||||
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
|
|
||||||
const Selection::InstanceIdxsList * instlist = obj_sel[oidx];
|
|
||||||
ModelObject *mo = model.objects[oidx];
|
|
||||||
|
|
||||||
std::vector<bool> inst_sel(mo->instances.size(), false);
|
|
||||||
|
|
||||||
if (instlist)
|
|
||||||
for (auto inst_id : *instlist)
|
|
||||||
inst_sel[size_t(inst_id)] = true;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < inst_sel.size(); ++i) {
|
|
||||||
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
|
|
||||||
|
|
||||||
ArrangePolygons &cont = mo->instances[i]->printable ?
|
|
||||||
(inst_sel[i] ? m_selected :
|
|
||||||
m_unselected) :
|
|
||||||
m_unprintable;
|
|
||||||
|
|
||||||
cont.emplace_back(std::move(ap));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& wti = plater().updated_wipe_tower();
|
|
||||||
if (wti) {
|
|
||||||
ArrangePolygon &&ap = get_arrange_poly(&wti);
|
|
||||||
|
|
||||||
plater().get_selection().is_wipe_tower() ?
|
|
||||||
m_selected.emplace_back(std::move(ap)) :
|
|
||||||
m_unselected.emplace_back(std::move(ap));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the selection was empty arrange everything
|
|
||||||
if (m_selected.empty()) m_selected.swap(m_unselected);
|
|
||||||
|
|
||||||
// The strides have to be removed from the fixed items. For the
|
|
||||||
// arrangeable (selected) items bed_idx is ignored and the
|
|
||||||
// translation is irrelevant.
|
|
||||||
for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
void prepare() override
|
|
||||||
{
|
|
||||||
wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
using PlaterJob::PlaterJob;
|
|
||||||
|
|
||||||
int status_range() const override
|
|
||||||
{
|
|
||||||
return int(m_selected.size() + m_unprintable.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void process() override;
|
|
||||||
|
|
||||||
void finalize() override {
|
|
||||||
// Ignore the arrange result if aborted.
|
|
||||||
if (was_canceled()) return;
|
|
||||||
|
|
||||||
// Unprintable items go to the last virtual bed
|
|
||||||
int beds = 0;
|
|
||||||
|
|
||||||
// Apply the arrange result to all selected objects
|
|
||||||
for (ArrangePolygon &ap : m_selected) {
|
|
||||||
beds = std::max(ap.bed_idx, beds);
|
|
||||||
ap.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the virtual beds from the unselected items
|
|
||||||
for (ArrangePolygon &ap : m_unselected)
|
|
||||||
beds = std::max(ap.bed_idx, beds);
|
|
||||||
|
|
||||||
// Move the unprintable items to the last virtual bed.
|
|
||||||
for (ArrangePolygon &ap : m_unprintable) {
|
|
||||||
ap.bed_idx += beds + 1;
|
|
||||||
ap.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
plater().update();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class RotoptimizeJob : public PlaterJob
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using PlaterJob::PlaterJob;
|
|
||||||
void process() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Jobs defined inside the group class will be managed so that only one can
|
// 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
|
// run at a time. Also, the background process will be stopped if a job is
|
||||||
// started.
|
// started. It is up the the plater to ensure that the background slicing
|
||||||
class ExclusiveJobGroup {
|
// can't be restarted while a ui job is still running.
|
||||||
|
class Jobs: public ExclusiveJobGroup
|
||||||
|
{
|
||||||
|
priv *m;
|
||||||
|
size_t m_arrange_id, m_rotoptimize_id;
|
||||||
|
|
||||||
static const int ABORT_WAIT_MAX_MS = 10000;
|
void before_start() override { m->background_process.stop(); }
|
||||||
|
|
||||||
priv * m_plater;
|
|
||||||
|
|
||||||
ArrangeJob arrange_job{m_plater};
|
|
||||||
RotoptimizeJob rotoptimize_job{m_plater};
|
|
||||||
|
|
||||||
// To create a new job, just define a new subclass of Job, implement
|
|
||||||
// the process and the optional prepare() and finalize() methods
|
|
||||||
// Register the instance of the class in the m_jobs container
|
|
||||||
// if it cannot run concurrently with other jobs in this group
|
|
||||||
|
|
||||||
std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job,
|
|
||||||
rotoptimize_job};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {}
|
Jobs(priv *_m) : m(_m)
|
||||||
|
|
||||||
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);
|
m_arrange_id = add_job(std::make_unique<ArrangeJob>(m->statusbar(), m->q));
|
||||||
|
m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m->statusbar(), m->q));
|
||||||
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); }
|
void arrange()
|
||||||
|
|
||||||
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->take_snapshot(_(L("Arrange")));
|
||||||
m_jobs.end(),
|
start(m_arrange_id);
|
||||||
[](const Job &j) { return j.is_running(); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} m_ui_jobs{this};
|
void optimize_rotation()
|
||||||
|
{
|
||||||
|
m->take_snapshot(_(L("Optimize Rotation")));
|
||||||
|
start(m_rotoptimize_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
} m_ui_jobs;
|
||||||
|
|
||||||
bool delayed_scene_refresh;
|
bool delayed_scene_refresh;
|
||||||
std::string delayed_error_message;
|
std::string delayed_error_message;
|
||||||
@ -1808,10 +1527,10 @@ struct Plater::priv
|
|||||||
priv(Plater *q, MainFrame *main_frame);
|
priv(Plater *q, MainFrame *main_frame);
|
||||||
~priv();
|
~priv();
|
||||||
|
|
||||||
enum class UpdateParams {
|
enum class UpdateParams {
|
||||||
FORCE_FULL_SCREEN_REFRESH = 1,
|
FORCE_FULL_SCREEN_REFRESH = 1,
|
||||||
FORCE_BACKGROUND_PROCESSING_UPDATE = 2,
|
FORCE_BACKGROUND_PROCESSING_UPDATE = 2,
|
||||||
POSTPONE_VALIDATION_ERROR_MESSAGE = 4,
|
POSTPONE_VALIDATION_ERROR_MESSAGE = 4,
|
||||||
};
|
};
|
||||||
void update(unsigned int flags = 0);
|
void update(unsigned int flags = 0);
|
||||||
void select_view(const std::string& direction);
|
void select_view(const std::string& direction);
|
||||||
@ -1847,9 +1566,7 @@ struct Plater::priv
|
|||||||
std::string get_config(const std::string &key) const;
|
std::string get_config(const std::string &key) const;
|
||||||
BoundingBoxf bed_shape_bb() const;
|
BoundingBoxf bed_shape_bb() const;
|
||||||
BoundingBox scaled_bed_shape_bb() const;
|
BoundingBox scaled_bed_shape_bb() const;
|
||||||
arrangement::BedShapeHint get_bed_shape_hint() const;
|
|
||||||
|
|
||||||
void find_new_position(const ModelInstancePtrs &instances, coord_t min_d);
|
|
||||||
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config);
|
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config);
|
||||||
std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects);
|
std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects);
|
||||||
wxString get_export_file(GUI::FileType file_type);
|
wxString get_export_file(GUI::FileType file_type);
|
||||||
@ -1867,8 +1584,6 @@ struct Plater::priv
|
|||||||
void delete_object_from_model(size_t obj_idx);
|
void delete_object_from_model(size_t obj_idx);
|
||||||
void reset();
|
void reset();
|
||||||
void mirror(Axis axis);
|
void mirror(Axis axis);
|
||||||
void arrange();
|
|
||||||
void sla_optimize_rotation();
|
|
||||||
void split_object();
|
void split_object();
|
||||||
void split_volume();
|
void split_volume();
|
||||||
void scale_selection_to_fit_print_volume();
|
void scale_selection_to_fit_print_volume();
|
||||||
@ -2035,6 +1750,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||||||
"support_material", "support_material_extruder", "support_material_interface_extruder", "support_material_contact_distance", "raft_layers"
|
"support_material", "support_material_extruder", "support_material_interface_extruder", "support_material_contact_distance", "raft_layers"
|
||||||
}))
|
}))
|
||||||
, sidebar(new Sidebar(q))
|
, sidebar(new Sidebar(q))
|
||||||
|
, m_ui_jobs(this)
|
||||||
, delayed_scene_refresh(false)
|
, delayed_scene_refresh(false)
|
||||||
, view_toolbar(GLToolbar::Radio, "View")
|
, view_toolbar(GLToolbar::Radio, "View")
|
||||||
, m_project_filename(wxEmptyString)
|
, m_project_filename(wxEmptyString)
|
||||||
@ -2110,14 +1826,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||||||
sidebar->Bind(EVT_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); });
|
sidebar->Bind(EVT_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); });
|
||||||
|
|
||||||
wxGLCanvas* view3D_canvas = view3D->get_wxglcanvas();
|
wxGLCanvas* view3D_canvas = view3D->get_wxglcanvas();
|
||||||
|
|
||||||
// 3DScene events:
|
// 3DScene events:
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); });
|
view3D_canvas->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); });
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this);
|
view3D_canvas->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this);
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this);
|
view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this);
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); });
|
view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); });
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); });
|
view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); });
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); });
|
view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); });
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
|
view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int> &evt)
|
view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int> &evt)
|
||||||
{ if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); });
|
{ if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); });
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); });
|
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); });
|
||||||
@ -2142,7 +1859,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||||||
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
|
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
|
||||||
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); });
|
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); });
|
||||||
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); });
|
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); });
|
||||||
view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { arrange(); });
|
view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); });
|
||||||
view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); });
|
view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); });
|
||||||
view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); });
|
view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); });
|
||||||
view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); });
|
view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); });
|
||||||
@ -2810,40 +2527,12 @@ void Plater::priv::mirror(Axis axis)
|
|||||||
view3D->mirror_selection(axis);
|
view3D->mirror_selection(axis);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Plater::priv::arrange()
|
void Plater::find_new_position(const ModelInstancePtrs &instances,
|
||||||
{
|
|
||||||
this->take_snapshot(_L("Arrange"));
|
|
||||||
m_ui_jobs.start(Jobs::Arrange);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 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() {
|
|
||||||
this->take_snapshot(_L("Optimize Rotation"));
|
|
||||||
m_ui_jobs.start(Jobs::Rotoptimize);
|
|
||||||
}
|
|
||||||
|
|
||||||
arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const {
|
|
||||||
|
|
||||||
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
|
|
||||||
assert(bed_shape_opt);
|
|
||||||
|
|
||||||
if (!bed_shape_opt) return {};
|
|
||||||
|
|
||||||
auto &bedpoints = bed_shape_opt->values;
|
|
||||||
Polyline bedpoly; bedpoly.points.reserve(bedpoints.size());
|
|
||||||
for (auto &v : bedpoints) bedpoly.append(scaled(v));
|
|
||||||
|
|
||||||
return arrangement::BedShapeHint(bedpoly);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Plater::priv::find_new_position(const ModelInstancePtrs &instances,
|
|
||||||
coord_t min_d)
|
coord_t min_d)
|
||||||
{
|
{
|
||||||
arrangement::ArrangePolygons movable, fixed;
|
arrangement::ArrangePolygons movable, fixed;
|
||||||
|
|
||||||
for (const ModelObject *mo : model.objects)
|
for (const ModelObject *mo : p->model.objects)
|
||||||
for (const ModelInstance *inst : mo->instances) {
|
for (const ModelInstance *inst : mo->instances) {
|
||||||
auto it = std::find(instances.begin(), instances.end(), inst);
|
auto it = std::find(instances.begin(), instances.end(), inst);
|
||||||
auto arrpoly = inst->get_arrange_polygon();
|
auto arrpoly = inst->get_arrange_polygon();
|
||||||
@ -2854,10 +2543,11 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances,
|
|||||||
movable.emplace_back(std::move(arrpoly));
|
movable.emplace_back(std::move(arrpoly));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updated_wipe_tower())
|
if (p->view3D->get_canvas3d()->get_wipe_tower_info())
|
||||||
fixed.emplace_back(wipetower.get_arrange_polygon());
|
fixed.emplace_back(get_wipe_tower_arrangepoly(*this));
|
||||||
|
|
||||||
arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint());
|
arrangement::arrange(movable, fixed, get_bed_shape(*config()),
|
||||||
|
arrangement::ArrangeParams{min_d});
|
||||||
|
|
||||||
for (size_t i = 0; i < instances.size(); ++i)
|
for (size_t i = 0; i < instances.size(); ++i)
|
||||||
if (movable[i].bed_idx == 0)
|
if (movable[i].bed_idx == 0)
|
||||||
@ -2865,90 +2555,6 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances,
|
|||||||
movable[i].rotation);
|
movable[i].rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Plater::priv::ArrangeJob::process() {
|
|
||||||
static const auto arrangestr = _L("Arranging");
|
|
||||||
|
|
||||||
double dist = min_object_distance(*plater().config);
|
|
||||||
|
|
||||||
coord_t min_d = scaled(dist);
|
|
||||||
auto count = unsigned(m_selected.size() + m_unprintable.size());
|
|
||||||
arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint();
|
|
||||||
|
|
||||||
auto stopfn = [this]() { return was_canceled(); };
|
|
||||||
|
|
||||||
try {
|
|
||||||
arrangement::arrange(m_selected, m_unselected, min_d, bedshape,
|
|
||||||
[this, count](unsigned st) {
|
|
||||||
st += m_unprintable.size();
|
|
||||||
if (st > 0) update_status(int(count - st), arrangestr);
|
|
||||||
}, stopfn);
|
|
||||||
arrangement::arrange(m_unprintable, {}, min_d, bedshape,
|
|
||||||
[this, count](unsigned st) {
|
|
||||||
if (st > 0) update_status(int(count - st), arrangestr);
|
|
||||||
}, stopfn);
|
|
||||||
} catch (std::exception & /*e*/) {
|
|
||||||
GUI::show_error(plater().q,
|
|
||||||
_L("Could not arrange model objects! "
|
|
||||||
"Some geometries may be invalid."));
|
|
||||||
}
|
|
||||||
|
|
||||||
// finalize just here.
|
|
||||||
update_status(int(count),
|
|
||||||
was_canceled() ? _L("Arranging canceled.")
|
|
||||||
: _L("Arranging done."));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Plater::priv::RotoptimizeJob::process()
|
|
||||||
{
|
|
||||||
int obj_idx = plater().get_selected_object_idx();
|
|
||||||
if (obj_idx < 0) { return; }
|
|
||||||
|
|
||||||
ModelObject *o = plater().model.objects[size_t(obj_idx)];
|
|
||||||
|
|
||||||
auto r = sla::find_best_rotation(
|
|
||||||
*o,
|
|
||||||
.005f,
|
|
||||||
[this](unsigned s) {
|
|
||||||
if (s < 100)
|
|
||||||
update_status(int(s),
|
|
||||||
_L("Searching for optimal orientation"));
|
|
||||||
},
|
|
||||||
[this]() { return was_canceled(); });
|
|
||||||
|
|
||||||
|
|
||||||
double mindist = 6.0; // FIXME
|
|
||||||
|
|
||||||
if (!was_canceled()) {
|
|
||||||
for(ModelInstance * oi : o->instances) {
|
|
||||||
oi->set_rotation({r[X], r[Y], r[Z]});
|
|
||||||
|
|
||||||
auto trmatrix = oi->get_transformation().get_matrix();
|
|
||||||
Polygon trchull = o->convex_hull_2d(trmatrix);
|
|
||||||
|
|
||||||
MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
|
|
||||||
double r = rotbb.angle_to_X();
|
|
||||||
|
|
||||||
// The box should be landscape
|
|
||||||
if(rotbb.width() < rotbb.height()) r += PI / 2;
|
|
||||||
|
|
||||||
Vec3d rt = oi->get_rotation(); rt(Z) += r;
|
|
||||||
|
|
||||||
oi->set_rotation(rt);
|
|
||||||
}
|
|
||||||
|
|
||||||
plater().find_new_position(o->instances, scaled(mindist));
|
|
||||||
|
|
||||||
// Correct the z offset of the object which was corrupted be
|
|
||||||
// the rotation
|
|
||||||
o->ensure_on_bed();
|
|
||||||
}
|
|
||||||
|
|
||||||
update_status(100,
|
|
||||||
was_canceled() ? _L("Orientation search canceled.")
|
|
||||||
: _L("Orientation found."));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Plater::priv::split_object()
|
void Plater::priv::split_object()
|
||||||
{
|
{
|
||||||
int obj_idx = get_selected_object_idx();
|
int obj_idx = get_selected_object_idx();
|
||||||
@ -3589,7 +3195,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update plater with new config
|
// update plater with new config
|
||||||
wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config());
|
q->on_config_change(wxGetApp().preset_bundle->full_config());
|
||||||
/* Settings list can be changed after printer preset changing, so
|
/* Settings list can be changed after printer preset changing, so
|
||||||
* update all settings items for all item had it.
|
* update all settings items for all item had it.
|
||||||
* Furthermore, Layers editing is implemented only for FFF printers
|
* Furthermore, Layers editing is implemented only for FFF printers
|
||||||
@ -4036,8 +3642,12 @@ bool Plater::priv::complit_init_sla_object_menu()
|
|||||||
sla_object_menu.AppendSeparator();
|
sla_object_menu.AppendSeparator();
|
||||||
|
|
||||||
// Add the automatic rotation sub-menu
|
// Add the automatic rotation sub-menu
|
||||||
append_menu_item(&sla_object_menu, wxID_ANY, _L("Optimize orientation"), _L("Optimize the rotation of the object for better print results."),
|
append_menu_item(
|
||||||
[this](wxCommandEvent&) { sla_optimize_rotation(); });
|
&sla_object_menu, wxID_ANY, _(L("Optimize orientation")),
|
||||||
|
_(L("Optimize the rotation of the object for better print results.")),
|
||||||
|
[this](wxCommandEvent &) {
|
||||||
|
m_ui_jobs.optimize_rotation();
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -4733,7 +4343,7 @@ void Plater::increase_instances(size_t num)
|
|||||||
sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num);
|
sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num);
|
||||||
|
|
||||||
if (p->get_config("autocenter") == "1")
|
if (p->get_config("autocenter") == "1")
|
||||||
p->arrange();
|
arrange();
|
||||||
|
|
||||||
p->update();
|
p->update();
|
||||||
|
|
||||||
@ -5467,6 +5077,11 @@ bool Plater::is_export_gcode_scheduled() const
|
|||||||
return p->background_process.is_export_scheduled();
|
return p->background_process.is_export_scheduled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Selection &Plater::get_selection() const
|
||||||
|
{
|
||||||
|
return p->get_selection();
|
||||||
|
}
|
||||||
|
|
||||||
int Plater::get_selected_object_idx()
|
int Plater::get_selected_object_idx()
|
||||||
{
|
{
|
||||||
return p->get_selected_object_idx();
|
return p->get_selected_object_idx();
|
||||||
@ -5492,6 +5107,11 @@ BoundingBoxf Plater::bed_shape_bb() const
|
|||||||
return p->bed_shape_bb();
|
return p->bed_shape_bb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Plater::arrange()
|
||||||
|
{
|
||||||
|
p->m_ui_jobs.arrange();
|
||||||
|
}
|
||||||
|
|
||||||
void Plater::set_current_canvas_as_dirty()
|
void Plater::set_current_canvas_as_dirty()
|
||||||
{
|
{
|
||||||
p->set_current_canvas_as_dirty();
|
p->set_current_canvas_as_dirty();
|
||||||
@ -5514,6 +5134,8 @@ PrinterTechnology Plater::printer_technology() const
|
|||||||
return p->printer_technology;
|
return p->printer_technology;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DynamicPrintConfig * Plater::config() const { return p->config; }
|
||||||
|
|
||||||
void Plater::set_printer_technology(PrinterTechnology printer_technology)
|
void Plater::set_printer_technology(PrinterTechnology printer_technology)
|
||||||
{
|
{
|
||||||
p->printer_technology = printer_technology;
|
p->printer_technology = printer_technology;
|
||||||
|
@ -9,8 +9,10 @@
|
|||||||
#include <wx/bmpcbox.h>
|
#include <wx/bmpcbox.h>
|
||||||
|
|
||||||
#include "Preset.hpp"
|
#include "Preset.hpp"
|
||||||
|
#include "Selection.hpp"
|
||||||
|
|
||||||
#include "libslic3r/BoundingBox.hpp"
|
#include "libslic3r/BoundingBox.hpp"
|
||||||
|
#include "Job.hpp"
|
||||||
#include "wxExtensions.hpp"
|
#include "wxExtensions.hpp"
|
||||||
|
|
||||||
class wxButton;
|
class wxButton;
|
||||||
@ -253,12 +255,16 @@ public:
|
|||||||
|
|
||||||
bool is_export_gcode_scheduled() const;
|
bool is_export_gcode_scheduled() const;
|
||||||
|
|
||||||
|
const Selection& get_selection() const;
|
||||||
int get_selected_object_idx();
|
int get_selected_object_idx();
|
||||||
bool is_single_full_object_selection() const;
|
bool is_single_full_object_selection() const;
|
||||||
GLCanvas3D* canvas3D();
|
GLCanvas3D* canvas3D();
|
||||||
GLCanvas3D* get_current_canvas3D();
|
GLCanvas3D* get_current_canvas3D();
|
||||||
BoundingBoxf bed_shape_bb() const;
|
BoundingBoxf bed_shape_bb() const;
|
||||||
|
|
||||||
|
void arrange();
|
||||||
|
void find_new_position(const ModelInstancePtrs &instances, coord_t min_d);
|
||||||
|
|
||||||
void set_current_canvas_as_dirty();
|
void set_current_canvas_as_dirty();
|
||||||
#if ENABLE_NON_STATIC_CANVAS_MANAGER
|
#if ENABLE_NON_STATIC_CANVAS_MANAGER
|
||||||
void unbind_canvas_event_handlers();
|
void unbind_canvas_event_handlers();
|
||||||
@ -266,6 +272,7 @@ public:
|
|||||||
#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
|
#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
|
||||||
|
|
||||||
PrinterTechnology printer_technology() const;
|
PrinterTechnology printer_technology() const;
|
||||||
|
const DynamicPrintConfig * config() const;
|
||||||
void set_printer_technology(PrinterTechnology printer_technology);
|
void set_printer_technology(PrinterTechnology printer_technology);
|
||||||
|
|
||||||
void copy_selection_to_clipboard();
|
void copy_selection_to_clipboard();
|
||||||
@ -371,6 +378,7 @@ private:
|
|||||||
bool m_was_scheduled;
|
bool m_was_scheduled;
|
||||||
};
|
};
|
||||||
|
|
||||||
}}
|
} // namespace GUI
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
68
src/slic3r/GUI/RotoptimizeJob.cpp
Normal file
68
src/slic3r/GUI/RotoptimizeJob.cpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#include "RotoptimizeJob.hpp"
|
||||||
|
|
||||||
|
#include "libslic3r/MTUtils.hpp"
|
||||||
|
#include "libslic3r/SLA/Rotfinder.hpp"
|
||||||
|
#include "libslic3r/MinAreaBoundingBox.hpp"
|
||||||
|
|
||||||
|
#include "Plater.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r { namespace GUI {
|
||||||
|
|
||||||
|
void RotoptimizeJob::process()
|
||||||
|
{
|
||||||
|
int obj_idx = m_plater->get_selected_object_idx();
|
||||||
|
if (obj_idx < 0) { return; }
|
||||||
|
|
||||||
|
ModelObject *o = m_plater->model().objects[size_t(obj_idx)];
|
||||||
|
|
||||||
|
auto r = sla::find_best_rotation(
|
||||||
|
*o,
|
||||||
|
.005f,
|
||||||
|
[this](unsigned s) {
|
||||||
|
if (s < 100)
|
||||||
|
update_status(int(s),
|
||||||
|
_(L("Searching for optimal orientation")));
|
||||||
|
},
|
||||||
|
[this]() { return was_canceled(); });
|
||||||
|
|
||||||
|
|
||||||
|
double mindist = 6.0; // FIXME
|
||||||
|
|
||||||
|
if (!was_canceled()) {
|
||||||
|
for(ModelInstance * oi : o->instances) {
|
||||||
|
oi->set_rotation({r[X], r[Y], r[Z]});
|
||||||
|
|
||||||
|
auto trmatrix = oi->get_transformation().get_matrix();
|
||||||
|
Polygon trchull = o->convex_hull_2d(trmatrix);
|
||||||
|
|
||||||
|
MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
|
||||||
|
double phi = rotbb.angle_to_X();
|
||||||
|
|
||||||
|
// The box should be landscape
|
||||||
|
if(rotbb.width() < rotbb.height()) phi += PI / 2;
|
||||||
|
|
||||||
|
Vec3d rt = oi->get_rotation(); rt(Z) += phi;
|
||||||
|
|
||||||
|
oi->set_rotation(rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_plater->find_new_position(o->instances, scaled(mindist));
|
||||||
|
|
||||||
|
// Correct the z offset of the object which was corrupted be
|
||||||
|
// the rotation
|
||||||
|
o->ensure_on_bed();
|
||||||
|
}
|
||||||
|
|
||||||
|
update_status(100, was_canceled() ? _(L("Orientation search canceled.")) :
|
||||||
|
_(L("Orientation found.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RotoptimizeJob::finalize()
|
||||||
|
{
|
||||||
|
if (!was_canceled())
|
||||||
|
m_plater->update();
|
||||||
|
|
||||||
|
Job::finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
}}
|
24
src/slic3r/GUI/RotoptimizeJob.hpp
Normal file
24
src/slic3r/GUI/RotoptimizeJob.hpp
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#ifndef ROTOPTIMIZEJOB_HPP
|
||||||
|
#define ROTOPTIMIZEJOB_HPP
|
||||||
|
|
||||||
|
#include "Job.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r { namespace GUI {
|
||||||
|
|
||||||
|
class Plater;
|
||||||
|
|
||||||
|
class RotoptimizeJob : public Job
|
||||||
|
{
|
||||||
|
Plater *m_plater;
|
||||||
|
public:
|
||||||
|
RotoptimizeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||||
|
: Job{std::move(pri)}, m_plater{plater}
|
||||||
|
{}
|
||||||
|
|
||||||
|
void process() override;
|
||||||
|
void finalize() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespace Slic3r::GUI
|
||||||
|
|
||||||
|
#endif // ROTOPTIMIZEJOB_HPP
|
Loading…
x
Reference in New Issue
Block a user