mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-08-05 21:14:01 -06:00
Merge branch 'tm_sl1_import_2'
This commit is contained in:
commit
4f622e4541
75 changed files with 4203 additions and 2549 deletions
|
@ -142,12 +142,19 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/UpdateDialogs.hpp
|
||||
GUI/FirmwareDialog.cpp
|
||||
GUI/FirmwareDialog.hpp
|
||||
GUI/ProgressIndicator.hpp
|
||||
GUI/ProgressStatusBar.hpp
|
||||
GUI/ProgressStatusBar.cpp
|
||||
GUI/PrintHostDialogs.cpp
|
||||
GUI/PrintHostDialogs.hpp
|
||||
GUI/Job.hpp
|
||||
GUI/Jobs/Job.hpp
|
||||
GUI/Jobs/Job.cpp
|
||||
GUI/Jobs/ArrangeJob.hpp
|
||||
GUI/Jobs/ArrangeJob.cpp
|
||||
GUI/Jobs/RotoptimizeJob.hpp
|
||||
GUI/Jobs/RotoptimizeJob.cpp
|
||||
GUI/Jobs/SLAImportJob.hpp
|
||||
GUI/Jobs/SLAImportJob.cpp
|
||||
GUI/Jobs/ProgressIndicator.hpp
|
||||
GUI/ProgressStatusBar.hpp
|
||||
GUI/ProgressStatusBar.cpp
|
||||
GUI/Mouse3DController.cpp
|
||||
GUI/Mouse3DController.hpp
|
||||
GUI/DoubleSlider.cpp
|
||||
|
@ -177,6 +184,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
Utils/HexFile.cpp
|
||||
Utils/HexFile.hpp
|
||||
Utils/Thread.hpp
|
||||
Utils/SLAImport.hpp
|
||||
Utils/SLAImport.cpp
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/GCode/PostProcessor.hpp"
|
||||
#include "libslic3r/GCode/PreviewData.hpp"
|
||||
#include "libslic3r/Format/SL1.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include <cassert>
|
||||
|
@ -149,7 +150,7 @@ void BackgroundSlicingProcess::process_sla()
|
|||
const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
|
||||
|
||||
Zipper zipper(export_path);
|
||||
m_sla_print->export_raster(zipper);
|
||||
m_sla_archive.export_print(zipper, *m_sla_print);
|
||||
|
||||
if (m_thumbnail_cb != nullptr)
|
||||
{
|
||||
|
@ -473,9 +474,9 @@ void BackgroundSlicingProcess::prepare_upload()
|
|||
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
} else {
|
||||
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
|
||||
|
||||
Zipper zipper{source_path.string()};
|
||||
m_sla_print->export_raster(zipper, m_upload_job.upload_data.upload_path.string());
|
||||
m_sla_archive.export_print(zipper, *m_sla_print, m_upload_job.upload_data.upload_path.string());
|
||||
if (m_thumbnail_cb != nullptr)
|
||||
{
|
||||
ThumbnailsList thumbnails;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <wx/event.h>
|
||||
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/Format/SL1.hpp"
|
||||
#include "slic3r/Utils/PrintHost.hpp"
|
||||
|
||||
|
||||
|
@ -19,6 +20,7 @@ class DynamicPrintConfig;
|
|||
class GCodePreviewData;
|
||||
class Model;
|
||||
class SLAPrint;
|
||||
class SL1Archive;
|
||||
|
||||
class SlicingStatusEvent : public wxEvent
|
||||
{
|
||||
|
@ -47,7 +49,7 @@ public:
|
|||
~BackgroundSlicingProcess();
|
||||
|
||||
void set_fff_print(Print *print) { m_fff_print = print; }
|
||||
void set_sla_print(SLAPrint *print) { m_sla_print = print; }
|
||||
void set_sla_print(SLAPrint *print) { m_sla_print = print; m_sla_print->set_printer(&m_sla_archive); }
|
||||
void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
|
||||
void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
|
||||
|
||||
|
@ -155,6 +157,7 @@ private:
|
|||
GCodePreviewData *m_gcode_preview_data = nullptr;
|
||||
// Callback function, used to write thumbnails into gcode.
|
||||
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
|
||||
SL1Archive m_sla_archive;
|
||||
// Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
|
||||
std::string m_temp_output_path;
|
||||
// Output path provided by the user. The output path may be set even if the slicing is running,
|
||||
|
|
|
@ -2071,37 +2071,40 @@ void ObjectList::load_shape_object(const std::string& type_name)
|
|||
// Create mesh
|
||||
BoundingBoxf3 bb;
|
||||
TriangleMesh mesh = create_mesh(type_name, bb);
|
||||
load_mesh_object(mesh, _(L("Shape")) + "-" + _(type_name));
|
||||
}
|
||||
|
||||
void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name)
|
||||
{
|
||||
// Add mesh to model as a new object
|
||||
Model& model = wxGetApp().plater()->model();
|
||||
const wxString name = _(L("Shape")) + "-" + _(type_name);
|
||||
|
||||
#ifdef _DEBUG
|
||||
check_model_ids_validity(model);
|
||||
#endif /* _DEBUG */
|
||||
|
||||
|
||||
std::vector<size_t> object_idxs;
|
||||
ModelObject* new_object = model.add_object();
|
||||
new_object->name = into_u8(name);
|
||||
new_object->add_instance(); // each object should have at list one instance
|
||||
|
||||
|
||||
ModelVolume* new_volume = new_object->add_volume(mesh);
|
||||
new_volume->name = into_u8(name);
|
||||
// set a default extruder value, since user can't add it manually
|
||||
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
||||
new_object->invalidate_bounding_box();
|
||||
|
||||
|
||||
new_object->center_around_origin();
|
||||
new_object->ensure_on_bed();
|
||||
|
||||
|
||||
const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb();
|
||||
new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -new_object->origin_translation(2)));
|
||||
|
||||
|
||||
object_idxs.push_back(model.objects.size() - 1);
|
||||
#ifdef _DEBUG
|
||||
check_model_ids_validity(model);
|
||||
#endif /* _DEBUG */
|
||||
|
||||
|
||||
paste_objects_into_list(object_idxs);
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
|
|
@ -24,6 +24,7 @@ class ConfigOptionsGroup;
|
|||
class DynamicPrintConfig;
|
||||
class ModelObject;
|
||||
class ModelVolume;
|
||||
class TriangleMesh;
|
||||
enum class ModelVolumeType : int;
|
||||
|
||||
// FIXME: broken build on mac os because of this is missing:
|
||||
|
@ -265,6 +266,7 @@ public:
|
|||
void load_part(ModelObject* model_object, std::vector<std::pair<wxString, bool>> &volumes_info, ModelVolumeType type);
|
||||
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
|
||||
void load_shape_object(const std::string &type_name);
|
||||
void load_mesh_object(const TriangleMesh &mesh, const wxString &name);
|
||||
void del_object(const int obj_idx);
|
||||
void del_subobject_item(wxDataViewItem& item);
|
||||
void del_settings_from_config(const wxDataViewItem& parent_item);
|
||||
|
|
|
@ -1,155 +0,0 @@
|
|||
#ifndef JOB_HPP
|
||||
#define JOB_HPP
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <slic3r/Utils/Thread.hpp>
|
||||
#include <slic3r/GUI/I18N.hpp>
|
||||
#include <slic3r/GUI/ProgressIndicator.hpp>
|
||||
|
||||
#include <wx/event.h>
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// A class to handle UI jobs like arranging and optimizing rotation.
|
||||
// These are not instant jobs, the user has to be informed about their
|
||||
// state in the status progress indicator. On the other hand they are
|
||||
// separated from the background slicing process. Ideally, these jobs should
|
||||
// run when the background process is not running.
|
||||
//
|
||||
// TODO: A mechanism would be useful for blocking the plater interactions:
|
||||
// objects would be frozen for the user. In case of arrange, an animation
|
||||
// could be shown, or with the optimize orientations, partial results
|
||||
// could be displayed.
|
||||
class Job : public wxEvtHandler
|
||||
{
|
||||
int m_range = 100;
|
||||
boost::thread m_thread;
|
||||
std::atomic<bool> m_running{false}, m_canceled{false};
|
||||
bool m_finalized = false;
|
||||
std::shared_ptr<ProgressIndicator> m_progress;
|
||||
|
||||
void run()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
bool was_canceled() const { return m_canceled.load(); }
|
||||
|
||||
// Launched just before start(), a job can use it to prepare internals
|
||||
virtual void prepare() {}
|
||||
|
||||
// Launched when the job is finished. It refreshes the 3Dscene by def.
|
||||
virtual void finalize() { m_finalized = true; }
|
||||
|
||||
|
||||
public:
|
||||
Job(std::shared_ptr<ProgressIndicator> pri) : m_progress(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; }
|
||||
|
||||
Job(const Job &) = delete;
|
||||
Job(Job &&) = delete;
|
||||
Job &operator=(const Job &) = delete;
|
||||
Job &operator=(Job &&) = delete;
|
||||
|
||||
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 = 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
|
||||
// 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)
|
||||
{
|
||||
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(); }
|
||||
void cancel() { m_canceled.store(true); }
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // JOB_HPP
|
223
src/slic3r/GUI/Jobs/ArrangeJob.cpp
Normal file
223
src/slic3r/GUI/Jobs/ArrangeJob.cpp
Normal file
|
@ -0,0 +1,223 @@
|
|||
#include "ArrangeJob.hpp"
|
||||
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// Cache the wti info
|
||||
class WipeTower: public GLCanvas3D::WipeTowerInfo {
|
||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||
public:
|
||||
explicit WipeTower(const GLCanvas3D::WipeTowerInfo &wti)
|
||||
: GLCanvas3D::WipeTowerInfo(wti)
|
||||
{}
|
||||
|
||||
explicit WipeTower(GLCanvas3D::WipeTowerInfo &&wti)
|
||||
: GLCanvas3D::WipeTowerInfo(std::move(wti))
|
||||
{}
|
||||
|
||||
void apply_arrange_result(const Vec2d& tr, double rotation)
|
||||
{
|
||||
m_pos = unscaled(tr); m_rotation = rotation;
|
||||
apply_wipe_tower();
|
||||
}
|
||||
|
||||
ArrangePolygon get_arrange_polygon() const
|
||||
{
|
||||
Polygon ap({
|
||||
{coord_t(0), coord_t(0)},
|
||||
{scaled(m_bb_size(X)), coord_t(0)},
|
||||
{scaled(m_bb_size)},
|
||||
{coord_t(0), scaled(m_bb_size(Y))},
|
||||
{coord_t(0), coord_t(0)},
|
||||
});
|
||||
|
||||
ArrangePolygon ret;
|
||||
ret.poly.contour = std::move(ap);
|
||||
ret.translation = scaled(m_pos);
|
||||
ret.rotation = m_rotation;
|
||||
ret.priority++;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
static WipeTower get_wipe_tower(Plater &plater)
|
||||
{
|
||||
return WipeTower{plater.canvas3D()->get_wipe_tower_info()};
|
||||
}
|
||||
|
||||
void ArrangeJob::clear_input()
|
||||
{
|
||||
const Model &model = m_plater->model();
|
||||
|
||||
size_t count = 0, cunprint = 0; // To know how much space to reserve
|
||||
for (auto obj : model.objects)
|
||||
for (auto mi : obj->instances)
|
||||
mi->printable ? count++ : cunprint++;
|
||||
|
||||
m_selected.clear();
|
||||
m_unselected.clear();
|
||||
m_unprintable.clear();
|
||||
m_selected.reserve(count + 1 /* for optional wti */);
|
||||
m_unselected.reserve(count + 1 /* for optional wti */);
|
||||
m_unprintable.reserve(cunprint /* for optional wti */);
|
||||
}
|
||||
|
||||
double ArrangeJob::bed_stride() const {
|
||||
double bedwidth = m_plater->bed_shape_bb().size().x();
|
||||
return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
|
||||
}
|
||||
|
||||
void ArrangeJob::prepare_all() {
|
||||
clear_input();
|
||||
|
||||
for (ModelObject *obj: m_plater->model().objects)
|
||||
for (ModelInstance *mi : obj->instances) {
|
||||
ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
|
||||
cont.emplace_back(get_arrange_poly(mi));
|
||||
}
|
||||
|
||||
if (auto wti = get_wipe_tower(*m_plater))
|
||||
m_selected.emplace_back(wti.get_arrange_polygon());
|
||||
}
|
||||
|
||||
void ArrangeJob::prepare_selected() {
|
||||
clear_input();
|
||||
|
||||
Model &model = m_plater->model();
|
||||
double stride = bed_stride();
|
||||
|
||||
std::vector<const Selection::InstanceIdxsList *>
|
||||
obj_sel(model.objects.size(), nullptr);
|
||||
|
||||
for (auto &s : m_plater->get_selection().get_content())
|
||||
if (s.first < int(obj_sel.size()))
|
||||
obj_sel[size_t(s.first)] = &s.second;
|
||||
|
||||
// Go through the objects and check if inside the selection
|
||||
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
|
||||
const Selection::InstanceIdxsList * instlist = obj_sel[oidx];
|
||||
ModelObject *mo = model.objects[oidx];
|
||||
|
||||
std::vector<bool> inst_sel(mo->instances.size(), false);
|
||||
|
||||
if (instlist)
|
||||
for (auto inst_id : *instlist)
|
||||
inst_sel[size_t(inst_id)] = true;
|
||||
|
||||
for (size_t i = 0; i < inst_sel.size(); ++i) {
|
||||
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
|
||||
|
||||
ArrangePolygons &cont = mo->instances[i]->printable ?
|
||||
(inst_sel[i] ? m_selected :
|
||||
m_unselected) :
|
||||
m_unprintable;
|
||||
|
||||
cont.emplace_back(std::move(ap));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto wti = get_wipe_tower(*m_plater)) {
|
||||
ArrangePolygon &&ap = get_arrange_poly(&wti);
|
||||
|
||||
m_plater->get_selection().is_wipe_tower() ?
|
||||
m_selected.emplace_back(std::move(ap)) :
|
||||
m_unselected.emplace_back(std::move(ap));
|
||||
}
|
||||
|
||||
// If the selection was empty arrange everything
|
||||
if (m_selected.empty()) m_selected.swap(m_unselected);
|
||||
|
||||
// The strides have to be removed from the fixed items. For the
|
||||
// arrangeable (selected) items bed_idx is ignored and the
|
||||
// translation is irrelevant.
|
||||
for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride;
|
||||
}
|
||||
|
||||
void ArrangeJob::prepare()
|
||||
{
|
||||
wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all();
|
||||
}
|
||||
|
||||
void ArrangeJob::process()
|
||||
{
|
||||
static const auto arrangestr = _(L("Arranging"));
|
||||
|
||||
double dist = min_object_distance(*m_plater->config());
|
||||
|
||||
arrangement::ArrangeParams params;
|
||||
params.min_obj_distance = scaled(dist);
|
||||
|
||||
auto count = unsigned(m_selected.size() + m_unprintable.size());
|
||||
Points bedpts = get_bed_shape(*m_plater->config());
|
||||
|
||||
params.stopcondition = [this]() { return was_canceled(); };
|
||||
|
||||
try {
|
||||
params.progressind = [this, count](unsigned st) {
|
||||
st += m_unprintable.size();
|
||||
if (st > 0) update_status(int(count - st), arrangestr);
|
||||
};
|
||||
|
||||
arrangement::arrange(m_selected, m_unselected, bedpts, params);
|
||||
|
||||
params.progressind = [this, count](unsigned st) {
|
||||
if (st > 0) update_status(int(count - st), arrangestr);
|
||||
};
|
||||
|
||||
arrangement::arrange(m_unprintable, {}, bedpts, params);
|
||||
} catch (std::exception & /*e*/) {
|
||||
GUI::show_error(m_plater,
|
||||
_(L("Could not arrange model objects! "
|
||||
"Some geometries may be invalid.")));
|
||||
}
|
||||
|
||||
// finalize just here.
|
||||
update_status(int(count),
|
||||
was_canceled() ? _(L("Arranging canceled."))
|
||||
: _(L("Arranging done.")));
|
||||
}
|
||||
|
||||
void ArrangeJob::finalize() {
|
||||
// Ignore the arrange result if aborted.
|
||||
if (was_canceled()) return;
|
||||
|
||||
// Unprintable items go to the last virtual bed
|
||||
int beds = 0;
|
||||
|
||||
// Apply the arrange result to all selected objects
|
||||
for (ArrangePolygon &ap : m_selected) {
|
||||
beds = std::max(ap.bed_idx, beds);
|
||||
ap.apply();
|
||||
}
|
||||
|
||||
// Get the virtual beds from the unselected items
|
||||
for (ArrangePolygon &ap : m_unselected)
|
||||
beds = std::max(ap.bed_idx, beds);
|
||||
|
||||
// Move the unprintable items to the last virtual bed.
|
||||
for (ArrangePolygon &ap : m_unprintable) {
|
||||
ap.bed_idx += beds + 1;
|
||||
ap.apply();
|
||||
}
|
||||
|
||||
m_plater->update();
|
||||
|
||||
Job::finalize();
|
||||
}
|
||||
|
||||
arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &plater)
|
||||
{
|
||||
return WipeTower{plater.canvas3D()->get_wipe_tower_info()}.get_arrange_polygon();
|
||||
}
|
||||
|
||||
void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap)
|
||||
{
|
||||
WipeTower{plater.canvas3D()->get_wipe_tower_info()}.apply_arrange_result(ap.translation.cast<double>(), ap.rotation);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
77
src/slic3r/GUI/Jobs/ArrangeJob.hpp
Normal file
77
src/slic3r/GUI/Jobs/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/Jobs/Job.cpp
Normal file
121
src/slic3r/GUI/Jobs/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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
110
src/slic3r/GUI/Jobs/Job.hpp
Normal file
110
src/slic3r/GUI/Jobs/Job.hpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
#ifndef JOB_HPP
|
||||
#define JOB_HPP
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <slic3r/Utils/Thread.hpp>
|
||||
#include <slic3r/GUI/I18N.hpp>
|
||||
|
||||
#include "ProgressIndicator.hpp"
|
||||
|
||||
#include <wx/event.h>
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// A class to handle UI jobs like arranging and optimizing rotation.
|
||||
// These are not instant jobs, the user has to be informed about their
|
||||
// state in the status progress indicator. On the other hand they are
|
||||
// separated from the background slicing process. Ideally, these jobs should
|
||||
// run when the background process is not running.
|
||||
//
|
||||
// TODO: A mechanism would be useful for blocking the plater interactions:
|
||||
// objects would be frozen for the user. In case of arrange, an animation
|
||||
// could be shown, or with the optimize orientations, partial results
|
||||
// could be displayed.
|
||||
class Job : public wxEvtHandler
|
||||
{
|
||||
int m_range = 100;
|
||||
boost::thread m_thread;
|
||||
std::atomic<bool> m_running{false}, m_canceled{false};
|
||||
bool m_finalized = false;
|
||||
std::shared_ptr<ProgressIndicator> m_progress;
|
||||
|
||||
void run();
|
||||
|
||||
protected:
|
||||
// status range for a particular job
|
||||
virtual int status_range() const { return 100; }
|
||||
|
||||
// status update, to be used from the work thread (process() method)
|
||||
void update_status(int st, const wxString &msg = "");
|
||||
|
||||
bool was_canceled() const { return m_canceled.load(); }
|
||||
|
||||
// Launched just before start(), a job can use it to prepare internals
|
||||
virtual void prepare() {}
|
||||
|
||||
// Launched when the job is finished. It refreshes the 3Dscene by def.
|
||||
virtual void finalize() { m_finalized = true; }
|
||||
|
||||
public:
|
||||
Job(std::shared_ptr<ProgressIndicator> pri);
|
||||
|
||||
bool is_finalized() const { return m_finalized; }
|
||||
|
||||
Job(const Job &) = delete;
|
||||
Job(Job &&) = delete;
|
||||
Job &operator=(const Job &) = delete;
|
||||
Job &operator=(Job &&) = delete;
|
||||
|
||||
virtual void process() = 0;
|
||||
|
||||
void start();
|
||||
|
||||
// To wait for the running job and join the threads. False is
|
||||
// returned if the timeout has been reached and the job is still
|
||||
// running. Call cancel() before this fn if you want to explicitly
|
||||
// end the job.
|
||||
bool join(int timeout_ms = 0);
|
||||
|
||||
bool is_running() const { return m_running.load(); }
|
||||
void cancel() { m_canceled.store(true); }
|
||||
};
|
||||
|
||||
// Jobs defined inside the group class will be managed so that only one can
|
||||
// run at a time. Also, the background process will be stopped if a job is
|
||||
// started.
|
||||
class ExclusiveJobGroup
|
||||
{
|
||||
static const int ABORT_WAIT_MAX_MS = 10000;
|
||||
|
||||
std::vector<std::unique_ptr<GUI::Job>> m_jobs;
|
||||
|
||||
protected:
|
||||
virtual void before_start() {}
|
||||
|
||||
public:
|
||||
virtual ~ExclusiveJobGroup() = default;
|
||||
|
||||
size_t add_job(std::unique_ptr<GUI::Job> &&job)
|
||||
{
|
||||
m_jobs.emplace_back(std::move(job));
|
||||
return m_jobs.size() - 1;
|
||||
}
|
||||
|
||||
void start(size_t jid);
|
||||
|
||||
void cancel_all() { for (auto& j : m_jobs) j->cancel(); }
|
||||
|
||||
void join_all(int wait_ms = 0);
|
||||
|
||||
void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); }
|
||||
|
||||
bool is_any_running() const;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // JOB_HPP
|
68
src/slic3r/GUI/Jobs/RotoptimizeJob.cpp
Normal file
68
src/slic3r/GUI/Jobs/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 "slic3r/GUI/Plater.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
void RotoptimizeJob::process()
|
||||
{
|
||||
int obj_idx = m_plater->get_selected_object_idx();
|
||||
if (obj_idx < 0) { return; }
|
||||
|
||||
ModelObject *o = m_plater->model().objects[size_t(obj_idx)];
|
||||
|
||||
auto r = sla::find_best_rotation(
|
||||
*o,
|
||||
.005f,
|
||||
[this](unsigned s) {
|
||||
if (s < 100)
|
||||
update_status(int(s),
|
||||
_(L("Searching for optimal orientation")));
|
||||
},
|
||||
[this]() { return was_canceled(); });
|
||||
|
||||
|
||||
double mindist = 6.0; // FIXME
|
||||
|
||||
if (!was_canceled()) {
|
||||
for(ModelInstance * oi : o->instances) {
|
||||
oi->set_rotation({r[X], r[Y], r[Z]});
|
||||
|
||||
auto trmatrix = oi->get_transformation().get_matrix();
|
||||
Polygon trchull = o->convex_hull_2d(trmatrix);
|
||||
|
||||
MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
|
||||
double phi = rotbb.angle_to_X();
|
||||
|
||||
// The box should be landscape
|
||||
if(rotbb.width() < rotbb.height()) phi += PI / 2;
|
||||
|
||||
Vec3d rt = oi->get_rotation(); rt(Z) += phi;
|
||||
|
||||
oi->set_rotation(rt);
|
||||
}
|
||||
|
||||
m_plater->find_new_position(o->instances, scaled(mindist));
|
||||
|
||||
// Correct the z offset of the object which was corrupted be
|
||||
// the rotation
|
||||
o->ensure_on_bed();
|
||||
}
|
||||
|
||||
update_status(100, was_canceled() ? _(L("Orientation search canceled.")) :
|
||||
_(L("Orientation found.")));
|
||||
}
|
||||
|
||||
void RotoptimizeJob::finalize()
|
||||
{
|
||||
if (!was_canceled())
|
||||
m_plater->update();
|
||||
|
||||
Job::finalize();
|
||||
}
|
||||
|
||||
}}
|
24
src/slic3r/GUI/Jobs/RotoptimizeJob.hpp
Normal file
24
src/slic3r/GUI/Jobs/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
|
226
src/slic3r/GUI/Jobs/SLAImportJob.cpp
Normal file
226
src/slic3r/GUI/Jobs/SLAImportJob.cpp
Normal file
|
@ -0,0 +1,226 @@
|
|||
#include "SLAImportJob.hpp"
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/AppConfig.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/PresetBundle.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/Utils/SLAImport.hpp"
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/filepicker.h>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
enum class Sel { modelAndProfile, profileOnly, modelOnly};
|
||||
|
||||
class ImportDlg: public wxDialog {
|
||||
wxFilePickerCtrl *m_filepicker;
|
||||
wxComboBox *m_import_dropdown, *m_quality_dropdown;
|
||||
|
||||
public:
|
||||
ImportDlg(Plater *plater)
|
||||
: wxDialog{plater, wxID_ANY, "Import SLA archive"}
|
||||
{
|
||||
auto szvert = new wxBoxSizer{wxVERTICAL};
|
||||
auto szfilepck = new wxBoxSizer{wxHORIZONTAL};
|
||||
|
||||
m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
|
||||
from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")),
|
||||
"SL1 archive files (*.sl1, *.zip)|*.sl1;*.SL1;*.zip;*.ZIP",
|
||||
wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
szfilepck->Add(new wxStaticText(this, wxID_ANY, _(L("Import file: "))), 0, wxALIGN_CENTER);
|
||||
szfilepck->Add(m_filepicker, 1);
|
||||
szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
auto szchoices = new wxBoxSizer{wxHORIZONTAL};
|
||||
|
||||
static const std::vector<wxString> inp_choices = {
|
||||
_(L("Import model and profile")),
|
||||
_(L("Import profile only")),
|
||||
_(L("Import model only"))
|
||||
};
|
||||
|
||||
m_import_dropdown = new wxComboBox(
|
||||
this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize,
|
||||
inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
|
||||
|
||||
szchoices->Add(m_import_dropdown);
|
||||
szchoices->Add(new wxStaticText(this, wxID_ANY, _(L("Quality: "))), 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
static const std::vector<wxString> qual_choices = {
|
||||
_(L("Accurate")),
|
||||
_(L("Balanced")),
|
||||
_(L("Quick"))
|
||||
};
|
||||
|
||||
m_quality_dropdown = new wxComboBox(
|
||||
this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize,
|
||||
qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
|
||||
szchoices->Add(m_quality_dropdown);
|
||||
|
||||
m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
||||
if (get_selection() == Sel::profileOnly)
|
||||
m_quality_dropdown->Disable();
|
||||
else m_quality_dropdown->Enable();
|
||||
});
|
||||
|
||||
szvert->Add(szchoices, 0, wxALL, 5);
|
||||
szvert->AddStretchSpacer(1);
|
||||
auto szbtn = new wxBoxSizer(wxHORIZONTAL);
|
||||
szbtn->Add(new wxButton{this, wxID_CANCEL});
|
||||
szbtn->Add(new wxButton{this, wxID_OK});
|
||||
szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5);
|
||||
|
||||
SetSizerAndFit(szvert);
|
||||
}
|
||||
|
||||
Sel get_selection() const
|
||||
{
|
||||
int sel = m_import_dropdown->GetSelection();
|
||||
return Sel(std::min(int(Sel::modelOnly), std::max(0, sel)));
|
||||
}
|
||||
|
||||
Vec2i get_marchsq_windowsize() const
|
||||
{
|
||||
enum { Accurate, Balanced, Fast};
|
||||
|
||||
switch(m_quality_dropdown->GetSelection())
|
||||
{
|
||||
case Fast: return {8, 8};
|
||||
case Balanced: return {4, 4};
|
||||
default:
|
||||
case Accurate:
|
||||
return {2, 2};
|
||||
}
|
||||
}
|
||||
|
||||
wxString get_path() const
|
||||
{
|
||||
return m_filepicker->GetPath();
|
||||
}
|
||||
};
|
||||
|
||||
class SLAImportJob::priv {
|
||||
public:
|
||||
Plater *plater;
|
||||
|
||||
Sel sel = Sel::modelAndProfile;
|
||||
|
||||
TriangleMesh mesh;
|
||||
DynamicPrintConfig profile;
|
||||
wxString path;
|
||||
Vec2i win = {2, 2};
|
||||
std::string err;
|
||||
|
||||
priv(Plater *plt): plater{plt} {}
|
||||
};
|
||||
|
||||
SLAImportJob::SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
: Job{std::move(pri)}, p{std::make_unique<priv>(plater)}
|
||||
{}
|
||||
|
||||
SLAImportJob::~SLAImportJob() = default;
|
||||
|
||||
void SLAImportJob::process()
|
||||
{
|
||||
auto progr = [this](int s) {
|
||||
if (s < 100) update_status(int(s), _(L("Importing SLA archive")));
|
||||
return !was_canceled();
|
||||
};
|
||||
|
||||
if (p->path.empty()) return;
|
||||
|
||||
std::string path = p->path.ToUTF8().data();
|
||||
try {
|
||||
switch (p->sel) {
|
||||
case Sel::modelAndProfile:
|
||||
import_sla_archive(path, p->win, p->mesh, p->profile, progr);
|
||||
break;
|
||||
case Sel::modelOnly:
|
||||
import_sla_archive(path, p->win, p->mesh, progr);
|
||||
break;
|
||||
case Sel::profileOnly:
|
||||
import_sla_archive(path, p->profile);
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (std::exception &ex) {
|
||||
p->err = ex.what();
|
||||
}
|
||||
|
||||
update_status(100, was_canceled() ? _(L("Importing canceled.")) :
|
||||
_(L("Importing done.")));
|
||||
}
|
||||
|
||||
void SLAImportJob::reset()
|
||||
{
|
||||
p->sel = Sel::modelAndProfile;
|
||||
p->mesh = {};
|
||||
p->profile = {};
|
||||
p->win = {2, 2};
|
||||
p->path.Clear();
|
||||
}
|
||||
|
||||
void SLAImportJob::prepare()
|
||||
{
|
||||
reset();
|
||||
|
||||
ImportDlg dlg{p->plater};
|
||||
|
||||
if (dlg.ShowModal() == wxID_OK) {
|
||||
auto path = dlg.get_path();
|
||||
auto nm = wxFileName(path);
|
||||
p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : path.ToUTF8();
|
||||
p->sel = dlg.get_selection();
|
||||
p->win = dlg.get_marchsq_windowsize();
|
||||
} else {
|
||||
p->path = "";
|
||||
}
|
||||
}
|
||||
|
||||
void SLAImportJob::finalize()
|
||||
{
|
||||
// Ignore the arrange result if aborted.
|
||||
if (was_canceled()) return;
|
||||
|
||||
if (!p->err.empty()) {
|
||||
show_error(p->plater, p->err);
|
||||
p->err = "";
|
||||
return;
|
||||
}
|
||||
|
||||
std::string name = wxFileName(p->path).GetName().ToUTF8().data();
|
||||
|
||||
if (!p->profile.empty()) {
|
||||
const ModelObjectPtrs& objects = p->plater->model().objects;
|
||||
for (auto object : objects)
|
||||
if (object->volumes.size() > 1)
|
||||
{
|
||||
Slic3r::GUI::show_info(nullptr,
|
||||
_(L("You cannot load SLA project with a multi-part object on the bed")) + "\n\n" +
|
||||
_(L("Please check your object list before preset changing.")),
|
||||
_(L("Attention!")) );
|
||||
return;
|
||||
}
|
||||
|
||||
DynamicPrintConfig config = {};
|
||||
config.apply(SLAFullPrintConfig::defaults());
|
||||
config += std::move(p->profile);
|
||||
|
||||
wxGetApp().preset_bundle->load_config_model(name, std::move(config));
|
||||
wxGetApp().load_current_presets();
|
||||
}
|
||||
|
||||
if (!p->mesh.empty())
|
||||
p->plater->sidebar().obj_list()->load_mesh_object(p->mesh, name);
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
}}
|
31
src/slic3r/GUI/Jobs/SLAImportJob.hpp
Normal file
31
src/slic3r/GUI/Jobs/SLAImportJob.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#ifndef SLAIMPORTJOB_HPP
|
||||
#define SLAIMPORTJOB_HPP
|
||||
|
||||
#include "Job.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class Plater;
|
||||
|
||||
class SLAImportJob : public Job {
|
||||
class priv;
|
||||
|
||||
std::unique_ptr<priv> p;
|
||||
|
||||
public:
|
||||
SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater);
|
||||
~SLAImportJob();
|
||||
|
||||
void process() override;
|
||||
|
||||
void reset();
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
void finalize() override;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // SLAIMPORTJOB_HPP
|
|
@ -589,6 +589,11 @@ void MainFrame::init_menubar()
|
|||
append_menu_item(import_menu, wxID_ANY, _(L("Import STL/OBJ/AM&F/3MF")) + dots + "\tCtrl+I", _(L("Load a model")),
|
||||
[this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater", nullptr,
|
||||
[this](){return m_plater != nullptr; }, this);
|
||||
|
||||
append_menu_item(import_menu, wxID_ANY, _(L("Import SL1 archive")) + dots, _(L("Load an SL1 output archive")),
|
||||
[this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr,
|
||||
[this](){return m_plater != nullptr; }, this);
|
||||
|
||||
import_menu->AppendSeparator();
|
||||
append_menu_item(import_menu, wxID_ANY, _(L("Import &Config")) + dots + "\tCtrl+L", _(L("Load exported configuration file")),
|
||||
[this](wxCommandEvent&) { load_config_file(); }, "import_config", nullptr,
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/SLA/Hollowing.hpp"
|
||||
#include "libslic3r/SLA/Rotfinder.hpp"
|
||||
#include "libslic3r/SLA/SupportPoint.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
|
@ -44,13 +43,6 @@
|
|||
#include "libslic3r/SLAPrint.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_App.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
|
@ -69,7 +61,9 @@
|
|||
#include "Camera.hpp"
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "Tab.hpp"
|
||||
#include "Job.hpp"
|
||||
#include "Jobs/ArrangeJob.hpp"
|
||||
#include "Jobs/RotoptimizeJob.hpp"
|
||||
#include "Jobs/SLAImportJob.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "BackgroundSlicingProcess.hpp"
|
||||
#include "ProgressStatusBar.hpp"
|
||||
|
@ -1485,311 +1479,44 @@ struct Plater::priv
|
|||
BackgroundSlicingProcess background_process;
|
||||
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
|
||||
// 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;
|
||||
|
||||
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};
|
||||
|
||||
// started. It is up the the plater to ensure that the background slicing
|
||||
// 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, m_sla_import_id;
|
||||
|
||||
void before_start() override { m->background_process.stop(); }
|
||||
|
||||
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)
|
||||
Jobs(priv *_m) : m(_m)
|
||||
{
|
||||
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!";
|
||||
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));
|
||||
m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m->statusbar(), m->q));
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
void arrange()
|
||||
{
|
||||
return std::any_of(m_jobs.begin(),
|
||||
m_jobs.end(),
|
||||
[](const Job &j) { return j.is_running(); });
|
||||
m->take_snapshot(_(L("Arrange")));
|
||||
start(m_arrange_id);
|
||||
}
|
||||
|
||||
} m_ui_jobs{this};
|
||||
|
||||
void optimize_rotation()
|
||||
{
|
||||
m->take_snapshot(_(L("Optimize Rotation")));
|
||||
start(m_rotoptimize_id);
|
||||
}
|
||||
|
||||
void import_sla_arch()
|
||||
{
|
||||
m->take_snapshot(_(L("Import SLA archive")));
|
||||
start(m_sla_import_id);
|
||||
}
|
||||
|
||||
} m_ui_jobs;
|
||||
|
||||
bool delayed_scene_refresh;
|
||||
std::string delayed_error_message;
|
||||
|
@ -1808,10 +1535,10 @@ struct Plater::priv
|
|||
priv(Plater *q, MainFrame *main_frame);
|
||||
~priv();
|
||||
|
||||
enum class UpdateParams {
|
||||
FORCE_FULL_SCREEN_REFRESH = 1,
|
||||
FORCE_BACKGROUND_PROCESSING_UPDATE = 2,
|
||||
POSTPONE_VALIDATION_ERROR_MESSAGE = 4,
|
||||
enum class UpdateParams {
|
||||
FORCE_FULL_SCREEN_REFRESH = 1,
|
||||
FORCE_BACKGROUND_PROCESSING_UPDATE = 2,
|
||||
POSTPONE_VALIDATION_ERROR_MESSAGE = 4,
|
||||
};
|
||||
void update(unsigned int flags = 0);
|
||||
void select_view(const std::string& direction);
|
||||
|
@ -1847,9 +1574,7 @@ struct Plater::priv
|
|||
std::string get_config(const std::string &key) const;
|
||||
BoundingBoxf 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_model_objects(const ModelObjectPtrs &model_objects);
|
||||
wxString get_export_file(GUI::FileType file_type);
|
||||
|
@ -1867,8 +1592,6 @@ struct Plater::priv
|
|||
void delete_object_from_model(size_t obj_idx);
|
||||
void reset();
|
||||
void mirror(Axis axis);
|
||||
void arrange();
|
||||
void sla_optimize_rotation();
|
||||
void split_object();
|
||||
void split_volume();
|
||||
void scale_selection_to_fit_print_volume();
|
||||
|
@ -2035,6 +1758,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
"support_material", "support_material_extruder", "support_material_interface_extruder", "support_material_contact_distance", "raft_layers"
|
||||
}))
|
||||
, sidebar(new Sidebar(q))
|
||||
, m_ui_jobs(this)
|
||||
, delayed_scene_refresh(false)
|
||||
, view_toolbar(GLToolbar::Radio, "View")
|
||||
, m_project_filename(wxEmptyString)
|
||||
|
@ -2110,14 +1834,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
sidebar->Bind(EVT_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); });
|
||||
|
||||
wxGLCanvas* view3D_canvas = view3D->get_wxglcanvas();
|
||||
|
||||
// 3DScene events:
|
||||
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_RIGHT_CLICK, &priv::on_right_click, this);
|
||||
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_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)
|
||||
{ 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(); });
|
||||
|
@ -2142,7 +1867,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_DELETE, [q](SimpleEvent&) { q->remove_selected(); });
|
||||
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_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); });
|
||||
view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); });
|
||||
|
@ -2810,40 +2535,12 @@ void Plater::priv::mirror(Axis axis)
|
|||
view3D->mirror_selection(axis);
|
||||
}
|
||||
|
||||
void Plater::priv::arrange()
|
||||
{
|
||||
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,
|
||||
void Plater::find_new_position(const ModelInstancePtrs &instances,
|
||||
coord_t min_d)
|
||||
{
|
||||
arrangement::ArrangePolygons movable, fixed;
|
||||
|
||||
for (const ModelObject *mo : model.objects)
|
||||
|
||||
for (const ModelObject *mo : p->model.objects)
|
||||
for (const ModelInstance *inst : mo->instances) {
|
||||
auto it = std::find(instances.begin(), instances.end(), inst);
|
||||
auto arrpoly = inst->get_arrange_polygon();
|
||||
|
@ -2853,11 +2550,12 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances,
|
|||
else
|
||||
movable.emplace_back(std::move(arrpoly));
|
||||
}
|
||||
|
||||
if (updated_wipe_tower())
|
||||
fixed.emplace_back(wipetower.get_arrange_polygon());
|
||||
|
||||
arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint());
|
||||
|
||||
if (p->view3D->get_canvas3d()->get_wipe_tower_info())
|
||||
fixed.emplace_back(get_wipe_tower_arrangepoly(*this));
|
||||
|
||||
arrangement::arrange(movable, fixed, get_bed_shape(*config()),
|
||||
arrangement::ArrangeParams{min_d});
|
||||
|
||||
for (size_t i = 0; i < instances.size(); ++i)
|
||||
if (movable[i].bed_idx == 0)
|
||||
|
@ -2865,95 +2563,6 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances,
|
|||
movable[i].rotation);
|
||||
}
|
||||
|
||||
void Plater::priv::ArrangeJob::process() {
|
||||
static const auto arrangestr = _L("Arranging");
|
||||
|
||||
// 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.
|
||||
double dist = 6; // PrintConfig::min_object_distance(config);
|
||||
if (plater().printer_technology == ptFFF) {
|
||||
dist = PrintConfig::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()
|
||||
{
|
||||
int obj_idx = get_selected_object_idx();
|
||||
|
@ -3594,7 +3203,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
|
|||
}
|
||||
|
||||
// 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
|
||||
* update all settings items for all item had it.
|
||||
* Furthermore, Layers editing is implemented only for FFF printers
|
||||
|
@ -4041,8 +3650,12 @@ bool Plater::priv::complit_init_sla_object_menu()
|
|||
sla_object_menu.AppendSeparator();
|
||||
|
||||
// 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."),
|
||||
[this](wxCommandEvent&) { sla_optimize_rotation(); });
|
||||
append_menu_item(
|
||||
&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;
|
||||
}
|
||||
|
@ -4646,6 +4259,11 @@ void Plater::add_model()
|
|||
load_files(paths, true, false);
|
||||
}
|
||||
|
||||
void Plater::import_sl1_archive()
|
||||
{
|
||||
p->m_ui_jobs.import_sla_arch();
|
||||
}
|
||||
|
||||
void Plater::extract_config_from_project()
|
||||
{
|
||||
wxString input_file;
|
||||
|
@ -4738,7 +4356,7 @@ void Plater::increase_instances(size_t num)
|
|||
sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num);
|
||||
|
||||
if (p->get_config("autocenter") == "1")
|
||||
p->arrange();
|
||||
arrange();
|
||||
|
||||
p->update();
|
||||
|
||||
|
@ -5472,6 +5090,11 @@ bool Plater::is_export_gcode_scheduled() const
|
|||
return p->background_process.is_export_scheduled();
|
||||
}
|
||||
|
||||
const Selection &Plater::get_selection() const
|
||||
{
|
||||
return p->get_selection();
|
||||
}
|
||||
|
||||
int Plater::get_selected_object_idx()
|
||||
{
|
||||
return p->get_selected_object_idx();
|
||||
|
@ -5497,6 +5120,11 @@ BoundingBoxf Plater::bed_shape_bb() const
|
|||
return p->bed_shape_bb();
|
||||
}
|
||||
|
||||
void Plater::arrange()
|
||||
{
|
||||
p->m_ui_jobs.arrange();
|
||||
}
|
||||
|
||||
void Plater::set_current_canvas_as_dirty()
|
||||
{
|
||||
p->set_current_canvas_as_dirty();
|
||||
|
@ -5519,6 +5147,8 @@ PrinterTechnology Plater::printer_technology() const
|
|||
return p->printer_technology;
|
||||
}
|
||||
|
||||
const DynamicPrintConfig * Plater::config() const { return p->config; }
|
||||
|
||||
void Plater::set_printer_technology(PrinterTechnology printer_technology)
|
||||
{
|
||||
p->printer_technology = printer_technology;
|
||||
|
|
|
@ -9,8 +9,10 @@
|
|||
#include <wx/bmpcbox.h>
|
||||
|
||||
#include "Preset.hpp"
|
||||
#include "Selection.hpp"
|
||||
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "Jobs/Job.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
class wxButton;
|
||||
|
@ -157,6 +159,7 @@ public:
|
|||
void load_project();
|
||||
void load_project(const wxString& filename);
|
||||
void add_model();
|
||||
void import_sl1_archive();
|
||||
void extract_config_from_project();
|
||||
|
||||
std::vector<size_t> load_files(const std::vector<boost::filesystem::path>& input_files, bool load_model = true, bool load_config = true);
|
||||
|
@ -252,12 +255,16 @@ public:
|
|||
void set_project_filename(const wxString& filename);
|
||||
|
||||
bool is_export_gcode_scheduled() const;
|
||||
|
||||
|
||||
const Selection& get_selection() const;
|
||||
int get_selected_object_idx();
|
||||
bool is_single_full_object_selection() const;
|
||||
GLCanvas3D* canvas3D();
|
||||
GLCanvas3D* get_current_canvas3D();
|
||||
BoundingBoxf bed_shape_bb() const;
|
||||
|
||||
void arrange();
|
||||
void find_new_position(const ModelInstancePtrs &instances, coord_t min_d);
|
||||
|
||||
void set_current_canvas_as_dirty();
|
||||
#if ENABLE_NON_STATIC_CANVAS_MANAGER
|
||||
|
@ -266,6 +273,7 @@ public:
|
|||
#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
|
||||
|
||||
PrinterTechnology printer_technology() const;
|
||||
const DynamicPrintConfig * config() const;
|
||||
void set_printer_technology(PrinterTechnology printer_technology);
|
||||
|
||||
void copy_selection_to_clipboard();
|
||||
|
@ -371,6 +379,7 @@ private:
|
|||
bool m_was_scheduled;
|
||||
};
|
||||
|
||||
}}
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "ProgressIndicator.hpp"
|
||||
#include "Jobs/ProgressIndicator.hpp"
|
||||
|
||||
class wxTimer;
|
||||
class wxGauge;
|
||||
|
|
314
src/slic3r/Utils/SLAImport.cpp
Normal file
314
src/slic3r/Utils/SLAImport.cpp
Normal file
|
@ -0,0 +1,314 @@
|
|||
#include "SLAImport.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "libslic3r/SlicesToTriangleMesh.hpp"
|
||||
#include "libslic3r/MarchingSquares.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/SLA/RasterBase.hpp"
|
||||
#include "libslic3r/miniz_extension.hpp"
|
||||
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <wx/image.h>
|
||||
#include <wx/mstream.h>
|
||||
|
||||
namespace marchsq {
|
||||
|
||||
// Specialize this struct to register a raster type for the Marching squares alg
|
||||
template<> struct _RasterTraits<wxImage> {
|
||||
using Rst = wxImage;
|
||||
|
||||
// The type of pixel cell in the raster
|
||||
using ValueType = uint8_t;
|
||||
|
||||
// Value at a given position
|
||||
static uint8_t get(const Rst &rst, size_t row, size_t col)
|
||||
{
|
||||
return rst.GetRed(col, row);
|
||||
}
|
||||
|
||||
// Number of rows and cols of the raster
|
||||
static size_t rows(const Rst &rst) { return rst.GetHeight(); }
|
||||
static size_t cols(const Rst &rst) { return rst.GetWidth(); }
|
||||
};
|
||||
|
||||
} // namespace marchsq
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
struct ArchiveData {
|
||||
boost::property_tree::ptree profile, config;
|
||||
std::vector<sla::EncodedRaster> images;
|
||||
};
|
||||
|
||||
static const constexpr char *CONFIG_FNAME = "config.ini";
|
||||
static const constexpr char *PROFILE_FNAME = "prusaslicer.ini";
|
||||
|
||||
boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry,
|
||||
MZ_Archive & zip)
|
||||
{
|
||||
std::string buf(size_t(entry.m_uncomp_size), '\0');
|
||||
|
||||
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
|
||||
buf.data(), buf.size(), 0))
|
||||
throw std::runtime_error(zip.get_errorstr());
|
||||
|
||||
boost::property_tree::ptree tree;
|
||||
std::stringstream ss(buf);
|
||||
boost::property_tree::read_ini(ss, tree);
|
||||
return tree;
|
||||
}
|
||||
|
||||
sla::EncodedRaster read_png(const mz_zip_archive_file_stat &entry,
|
||||
MZ_Archive & zip,
|
||||
const std::string & name)
|
||||
{
|
||||
std::vector<uint8_t> buf(entry.m_uncomp_size);
|
||||
|
||||
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
|
||||
buf.data(), buf.size(), 0))
|
||||
throw std::runtime_error(zip.get_errorstr());
|
||||
|
||||
return sla::EncodedRaster(std::move(buf),
|
||||
name.empty() ? entry.m_filename : name);
|
||||
}
|
||||
|
||||
ArchiveData extract_sla_archive(const std::string &zipfname,
|
||||
const std::string &exclude)
|
||||
{
|
||||
ArchiveData arch;
|
||||
|
||||
// Little RAII
|
||||
struct Arch: public MZ_Archive {
|
||||
Arch(const std::string &fname) {
|
||||
if (!open_zip_reader(&arch, fname))
|
||||
throw std::runtime_error(get_errorstr());
|
||||
}
|
||||
|
||||
~Arch() { close_zip_reader(&arch); }
|
||||
} zip (zipfname);
|
||||
|
||||
mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch);
|
||||
|
||||
for (mz_uint i = 0; i < num_entries; ++i)
|
||||
{
|
||||
mz_zip_archive_file_stat entry;
|
||||
|
||||
if (mz_zip_reader_file_stat(&zip.arch, i, &entry))
|
||||
{
|
||||
std::string name = entry.m_filename;
|
||||
boost::algorithm::to_lower(name);
|
||||
|
||||
if (boost::algorithm::contains(name, exclude)) continue;
|
||||
|
||||
if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip);
|
||||
if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip);
|
||||
|
||||
if (boost::filesystem::path(name).extension().string() == ".png") {
|
||||
auto it = std::lower_bound(
|
||||
arch.images.begin(), arch.images.end(), sla::EncodedRaster({}, name),
|
||||
[](const sla::EncodedRaster &r1, const sla::EncodedRaster &r2) {
|
||||
return std::less<std::string>()(r1.extension(), r2.extension());
|
||||
});
|
||||
|
||||
arch.images.insert(it, read_png(entry, zip, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return arch;
|
||||
}
|
||||
|
||||
ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
|
||||
double px_w, double px_h)
|
||||
{
|
||||
ExPolygons polys; polys.reserve(rings.size());
|
||||
|
||||
for (const marchsq::Ring &ring : rings) {
|
||||
Polygon poly; Points &pts = poly.points;
|
||||
pts.reserve(ring.size());
|
||||
|
||||
for (const marchsq::Coord &crd : ring)
|
||||
pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h));
|
||||
|
||||
polys.emplace_back(poly);
|
||||
}
|
||||
|
||||
// reverse the raster transformations
|
||||
return union_ex(polys);
|
||||
}
|
||||
|
||||
template<class Fn> void foreach_vertex(ExPolygon &poly, Fn &&fn)
|
||||
{
|
||||
for (auto &p : poly.contour.points) fn(p);
|
||||
for (auto &h : poly.holes)
|
||||
for (auto &p : h.points) fn(p);
|
||||
}
|
||||
|
||||
void invert_raster_trafo(ExPolygons & expolys,
|
||||
const sla::RasterBase::Trafo &trafo,
|
||||
coord_t width,
|
||||
coord_t height)
|
||||
{
|
||||
for (auto &expoly : expolys) {
|
||||
if (trafo.mirror_y)
|
||||
foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); });
|
||||
|
||||
if (trafo.mirror_x)
|
||||
foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); });
|
||||
|
||||
expoly.translate(-trafo.center_x, -trafo.center_y);
|
||||
|
||||
if (trafo.flipXY)
|
||||
foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); });
|
||||
|
||||
if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) {
|
||||
expoly.contour.reverse();
|
||||
for (auto &h : expoly.holes) h.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RasterParams {
|
||||
sla::RasterBase::Trafo trafo; // Raster transformations
|
||||
coord_t width, height; // scaled raster dimensions (not resolution)
|
||||
double px_h, px_w; // pixel dimesions
|
||||
marchsq::Coord win; // marching squares window size
|
||||
};
|
||||
|
||||
RasterParams get_raster_params(const DynamicPrintConfig &cfg)
|
||||
{
|
||||
auto *opt_disp_cols = cfg.option<ConfigOptionInt>("display_pixels_x");
|
||||
auto *opt_disp_rows = cfg.option<ConfigOptionInt>("display_pixels_y");
|
||||
auto *opt_disp_w = cfg.option<ConfigOptionFloat>("display_width");
|
||||
auto *opt_disp_h = cfg.option<ConfigOptionFloat>("display_height");
|
||||
auto *opt_mirror_x = cfg.option<ConfigOptionBool>("display_mirror_x");
|
||||
auto *opt_mirror_y = cfg.option<ConfigOptionBool>("display_mirror_y");
|
||||
auto *opt_orient = cfg.option<ConfigOptionEnum<SLADisplayOrientation>>("display_orientation");
|
||||
|
||||
if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
|
||||
!opt_mirror_x || !opt_mirror_y || !opt_orient)
|
||||
throw std::runtime_error("Invalid SL1 file");
|
||||
|
||||
RasterParams rstp;
|
||||
|
||||
rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1);
|
||||
rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1);
|
||||
|
||||
sla::RasterBase::Trafo trafo{opt_orient->value == sladoLandscape ?
|
||||
sla::RasterBase::roLandscape :
|
||||
sla::RasterBase::roPortrait,
|
||||
{opt_mirror_x->value, opt_mirror_y->value}};
|
||||
|
||||
rstp.height = scaled(opt_disp_h->value);
|
||||
rstp.width = scaled(opt_disp_w->value);
|
||||
|
||||
return rstp;
|
||||
}
|
||||
|
||||
struct SliceParams { double layerh = 0., initial_layerh = 0.; };
|
||||
|
||||
SliceParams get_slice_params(const DynamicPrintConfig &cfg)
|
||||
{
|
||||
auto *opt_layerh = cfg.option<ConfigOptionFloat>("layer_height");
|
||||
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
|
||||
|
||||
if (!opt_layerh || !opt_init_layerh)
|
||||
throw std::runtime_error("Invalid SL1 file");
|
||||
|
||||
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
|
||||
}
|
||||
|
||||
std::vector<ExPolygons> extract_slices_from_sla_archive(
|
||||
ArchiveData & arch,
|
||||
const RasterParams & rstp,
|
||||
std::function<bool(int)> progr)
|
||||
{
|
||||
auto jobdir = arch.config.get<std::string>("jobDir");
|
||||
for (auto &c : jobdir) c = std::tolower(c);
|
||||
|
||||
std::vector<ExPolygons> slices(arch.images.size());
|
||||
|
||||
struct Status
|
||||
{
|
||||
double incr, val, prev;
|
||||
bool stop = false;
|
||||
tbb::spin_mutex mutex;
|
||||
} st {100. / slices.size(), 0., 0.};
|
||||
|
||||
tbb::parallel_for(size_t(0), arch.images.size(),
|
||||
[&arch, &slices, &st, &rstp, progr](size_t i) {
|
||||
// Status indication guarded with the spinlock
|
||||
{
|
||||
std::lock_guard<tbb::spin_mutex> lck(st.mutex);
|
||||
if (st.stop) return;
|
||||
|
||||
st.val += st.incr;
|
||||
double curr = std::round(st.val);
|
||||
if (curr > st.prev) {
|
||||
st.prev = curr;
|
||||
st.stop = !progr(int(curr));
|
||||
}
|
||||
}
|
||||
|
||||
auto &buf = arch.images[i];
|
||||
wxMemoryInputStream stream{buf.data(), buf.size()};
|
||||
wxImage img{stream};
|
||||
|
||||
auto rings = marchsq::execute(img, 128, rstp.win);
|
||||
ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h);
|
||||
|
||||
// Invert the raster transformations indicated in
|
||||
// the profile metadata
|
||||
invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height);
|
||||
|
||||
slices[i] = std::move(expolys);
|
||||
});
|
||||
|
||||
if (st.stop) slices = {};
|
||||
|
||||
return slices;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
|
||||
{
|
||||
ArchiveData arch = extract_sla_archive(zipfname, "png");
|
||||
out.load(arch.profile);
|
||||
}
|
||||
|
||||
void import_sla_archive(
|
||||
const std::string & zipfname,
|
||||
Vec2i windowsize,
|
||||
TriangleMesh & out,
|
||||
DynamicPrintConfig & profile,
|
||||
std::function<bool(int)> progr)
|
||||
{
|
||||
// Ensure minimum window size for marching squares
|
||||
windowsize.x() = std::max(2, windowsize.x());
|
||||
windowsize.y() = std::max(2, windowsize.y());
|
||||
|
||||
ArchiveData arch = extract_sla_archive(zipfname, "thumbnail");
|
||||
profile.load(arch.profile);
|
||||
|
||||
RasterParams rstp = get_raster_params(profile);
|
||||
rstp.win = {windowsize.y(), windowsize.x()};
|
||||
|
||||
SliceParams slicp = get_slice_params(profile);
|
||||
|
||||
std::vector<ExPolygons> slices =
|
||||
extract_slices_from_sla_archive(arch, rstp, progr);
|
||||
|
||||
if (!slices.empty())
|
||||
out = slices_to_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
36
src/slic3r/Utils/SLAImport.hpp
Normal file
36
src/slic3r/Utils/SLAImport.hpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#ifndef SLAIMPORT_HPP
|
||||
#define SLAIMPORT_HPP
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/PrintConfig.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
class DynamicPrintConfig;
|
||||
|
||||
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
|
||||
|
||||
void import_sla_archive(
|
||||
const std::string & zipfname,
|
||||
Vec2i windowsize,
|
||||
TriangleMesh & out,
|
||||
DynamicPrintConfig & profile,
|
||||
std::function<bool(int)> progr = [](int) { return true; });
|
||||
|
||||
inline void import_sla_archive(
|
||||
const std::string & zipfname,
|
||||
Vec2i windowsize,
|
||||
TriangleMesh & out,
|
||||
std::function<bool(int)> progr = [](int) { return true; })
|
||||
{
|
||||
DynamicPrintConfig profile;
|
||||
import_sla_archive(zipfname, windowsize, out, profile, progr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // SLAIMPORT_HPP
|
Loading…
Add table
Add a link
Reference in a new issue